@simplysm/sd-claude 14.0.91 → 14.0.93

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 (93) hide show
  1. package/claude/references/sd-simplysm14/README.md +7 -6
  2. package/claude/references/sd-simplysm14/apis/angular/README.md +59 -39
  3. package/claude/references/sd-simplysm14/apis/angular/controls.md +119 -186
  4. package/claude/references/sd-simplysm14/apis/angular/crud.md +70 -31
  5. package/claude/references/sd-simplysm14/apis/angular/directives.md +55 -57
  6. package/claude/references/sd-simplysm14/apis/angular/features.md +86 -105
  7. package/claude/references/sd-simplysm14/apis/angular/infra.md +48 -57
  8. package/claude/references/sd-simplysm14/apis/angular/layout.md +37 -47
  9. package/claude/references/sd-simplysm14/apis/angular/overlay.md +82 -74
  10. package/claude/references/sd-simplysm14/apis/angular/routing-appstructure.md +61 -50
  11. package/claude/references/sd-simplysm14/apis/angular/shared-data.md +74 -57
  12. package/claude/references/sd-simplysm14/apis/angular/sheet.md +63 -72
  13. package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +23 -18
  14. package/claude/references/sd-simplysm14/apis/capacitor-plugin-file-system/README.md +21 -19
  15. package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +23 -18
  16. package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +72 -32
  17. package/claude/references/sd-simplysm14/apis/core-browser/README.md +18 -18
  18. package/claude/references/sd-simplysm14/apis/core-browser/dom-element.md +29 -29
  19. package/claude/references/sd-simplysm14/apis/core-browser/indexed-db.md +41 -41
  20. package/claude/references/sd-simplysm14/apis/core-common/README.md +97 -90
  21. package/claude/references/sd-simplysm14/apis/core-common/async-runtime.md +75 -51
  22. package/claude/references/sd-simplysm14/apis/core-common/collection-ext.md +81 -0
  23. package/claude/references/sd-simplysm14/apis/core-common/errors.md +27 -29
  24. package/claude/references/sd-simplysm14/apis/core-common/obj.md +44 -45
  25. package/claude/references/sd-simplysm14/apis/core-common/serialization.md +34 -33
  26. package/claude/references/sd-simplysm14/apis/core-common/value-types.md +86 -0
  27. package/claude/references/sd-simplysm14/apis/core-node/README.md +6 -6
  28. package/claude/references/sd-simplysm14/apis/core-node/consola.md +3 -0
  29. package/claude/references/sd-simplysm14/apis/core-node/cpx.md +2 -2
  30. package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +1 -1
  31. package/claude/references/sd-simplysm14/apis/core-node/fsx.md +2 -2
  32. package/claude/references/sd-simplysm14/apis/core-node/worker.md +6 -3
  33. package/claude/references/sd-simplysm14/apis/excel/README.md +10 -10
  34. package/claude/references/sd-simplysm14/apis/excel/conditional-format.md +4 -2
  35. package/claude/references/sd-simplysm14/apis/excel/utils.md +1 -1
  36. package/claude/references/sd-simplysm14/apis/excel/workbook-worksheet.md +6 -6
  37. package/claude/references/sd-simplysm14/apis/lint/README.md +6 -32
  38. package/claude/references/sd-simplysm14/apis/lint/recommended.md +60 -0
  39. package/claude/references/sd-simplysm14/apis/lint/rules.md +17 -17
  40. package/claude/references/sd-simplysm14/apis/orm-common/README.md +15 -6
  41. package/claude/references/sd-simplysm14/apis/orm-common/db-context.md +68 -102
  42. package/claude/references/sd-simplysm14/apis/orm-common/expr.md +75 -89
  43. package/claude/references/sd-simplysm14/apis/orm-common/queryable.md +87 -99
  44. package/claude/references/sd-simplysm14/apis/orm-common/schema.md +110 -147
  45. package/claude/references/sd-simplysm14/apis/orm-common/types.md +48 -51
  46. package/claude/references/sd-simplysm14/apis/orm-node/README.md +8 -13
  47. package/claude/references/sd-simplysm14/apis/orm-node/db-conn.md +5 -5
  48. package/claude/references/sd-simplysm14/apis/sd-cli/README.md +9 -6
  49. package/claude/references/sd-simplysm14/apis/sd-cli/SdTsCompiler.md +9 -8
  50. package/claude/references/sd-simplysm14/apis/sd-cli/sd-config-types.md +23 -19
  51. package/claude/references/sd-simplysm14/apis/service-client/README.md +20 -12
  52. package/claude/references/sd-simplysm14/apis/service-client/orm.md +6 -6
  53. package/claude/references/sd-simplysm14/apis/service-client/transport.md +1 -1
  54. package/claude/references/sd-simplysm14/apis/service-common/README.md +35 -32
  55. package/claude/references/sd-simplysm14/apis/service-common/app-structure.md +23 -22
  56. package/claude/references/sd-simplysm14/apis/service-common/protocol.md +23 -23
  57. package/claude/references/sd-simplysm14/apis/service-server/README.md +51 -43
  58. package/claude/references/sd-simplysm14/apis/service-server/service-authoring.md +6 -6
  59. package/claude/references/sd-simplysm14/apis/service-server/transport-internals.md +31 -21
  60. package/claude/references/sd-simplysm14/apis/service-server/v1-legacy.md +8 -8
  61. package/claude/references/sd-simplysm14/apis/storage/README.md +55 -49
  62. package/claude/references/sd-simplysm14/manuals/client-component.md +843 -740
  63. package/claude/references/sd-simplysm14/manuals/client-crud.md +8 -0
  64. package/claude/references/sd-simplysm14/manuals/client-demo.md +6 -16
  65. package/claude/references/sd-simplysm14/manuals/client-shared-data.md +26 -0
  66. package/claude/references/sd-simplysm14/manuals/logging.md +1 -1
  67. package/claude/references/sd-simplysm14/manuals/orm.md +15 -1
  68. package/claude/rules/sd-design-rules.md +7 -0
  69. package/claude/sd-system-prompt.md +5 -8
  70. package/claude/skills/sd-debug/SKILL.md +43 -0
  71. package/claude/skills/sd-debug/workflow.js +390 -0
  72. package/claude/skills/sd-demo/SKILL.md +18 -20
  73. package/claude/skills/sd-dev/SKILL.md +127 -24
  74. package/claude/skills/sd-docs/SKILL.md +5 -3
  75. package/claude/skills/sd-docs/references/subagent-prompt.md +2 -3
  76. package/claude/skills/sd-impl/SKILL.md +18 -18
  77. package/claude/skills/sd-manual/SKILL.md +1 -0
  78. package/claude/skills/sd-review/SKILL.md +24 -18
  79. package/claude/skills/sd-review/workflow.js +324 -0
  80. package/claude/skills/sd-spec/SKILL.md +96 -679
  81. package/claude/skills/sd-spec/references/example-spec.md +28 -50
  82. package/claude/skills/sd-spec/references/format-analyze.md +232 -0
  83. package/claude/skills/sd-spec/references/format-design.md +248 -0
  84. package/claude/skills/sd-spec/workflow-analyze.js +615 -0
  85. package/claude/skills/sd-spec/workflow-design.js +667 -0
  86. package/claude/skills/sd-unpack/scripts/handlers/office_com.py +5 -1
  87. package/package.json +1 -1
  88. package/scripts/postinstall.mjs +157 -18
  89. package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +0 -68
  90. package/claude/references/sd-simplysm14/apis/core-common/array-ext.md +0 -77
  91. package/claude/references/sd-simplysm14/apis/core-common/datetime.md +0 -86
  92. package/claude/skills/sd-skill/SKILL.md +0 -245
  93. package/claude/skills/sd-skill/scripts/run_eval.py +0 -380
@@ -20,7 +20,7 @@ WebSocket 1개의 연결·하트비트·자동 재연결 담당.
20
20
  - connected: boolean (getter) — 소켓이 OPEN 상태인지.
21
21
  - connect(): Promise\<void\> — 접속 시작. 실패 시 throw, 성공 시 재연결 카운트 리셋하고 `state: "connected"` emit.
22
22
  - close(): Promise\<void\> — 수동 종료. 이후 자동 재연결 안 함. 소켓 CLOSED 까지 대기 후 `state: "closed"` emit.
23
- - send(data: Bytes): Promise\<void\> — 바이트 전송. 일정 시간 내 미연결이면 throw("서버에 연결되지 않았습니다...").
23
+ - send(data: Bytes): Promise\<void\> — 바이트 전송. 일정 시간 내 미연결이면 throw("서버에 연결되지 않았습니다. 인터넷 연결을 확인해 주세요.").
24
24
  - on(type, listener) / off(type, listener) — 이벤트 구독/해제.
25
25
 
26
26
  `SocketProviderEvents`:
@@ -1,14 +1,15 @@
1
1
  # @simplysm/service-common
2
2
 
3
- 서버·클라이언트가 공유하는 서비스 통신 계약. 이벤트 정의, RPC 서비스 인터페이스,메뉴·권한 구조, 바이너리 와이어 프로토콜을 한곳에 둔다. 발생측·구독측 또는 서버·클라이언트가 같은 정의 객체·타입을 import 해 쓰는 게 핵심.
3
+ 서버·클라이언트가 공유하는 서비스 통신 계약. 이벤트 정의(`defineEvent`), 내장 RPC 서비스 인터페이스 시그니처, 메뉴·권한·모듈 구조(`AppStructure`), WebSocket 바이너리 프로토콜·메시지 타입을 한곳에 둔다. 발생측·구독측 또는 서버·클라이언트가 같은 정의 객체·타입을 import 해 쓰는 게 핵심.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **defineEvent / ServiceEventDef** — 서버↔클라이언트 또는 클라이언트끼리 실시간 이벤트(알림)를 주고받을 때 공통 패키지에서 이벤트를 정의. 정의 객체를 발생·구독 호출에 그대로 넘김.
8
- - **OrmService / AutoUpdateService / DbConnOptions** — 서버가 구현하고 클라이언트가 프록시로 호출하는 내장 RPC 서비스의 메서드 시그니처를 참조할 때.
7
+ - **defineEvent / ServiceEventDef** — 서버↔클라이언트 또는 클라이언트끼리 실시간 이벤트(알림)를 주고받을 때 공통 패키지에서 이벤트를 정의·export. 정의 객체를 발생·구독 호출에 그대로 넘김.
8
+ - **OrmService / DbConnOptions** — 서버가 구현하고 클라이언트가 프록시로 호출하는 내장 ORM RPC 서비스의 메서드 시그니처를 참조할 때.
9
+ - **AutoUpdateService** — 클라이언트 앱의 최신 버전 정보를 서버에 질의하는 내장 RPC 서비스 시그니처를 참조할 때.
9
10
  - **ServiceUploadResult** — 파일 업로드 응답 형태를 받아 처리할 때.
10
- - **앱 구조(AppStructure)** — 앱 메뉴 트리·화면 권한·기능 모듈 on/off 를 한 배열로 정의하거나, 그 배열에서 평탄 권한 목록을 뽑거나 모듈 활성 여부를 판정할 때. 자세히: [app-structure.md](./app-structure.md)
11
- - **서비스 프로토콜(Protocol)** — WebSocket 위 메시지의 인코딩·청킹·재조립을 직접 다루거나(서비스 클라이언트/서버 내부, worker 위임), 메시지 타입·크기 한계를 확인할 때. 자세히: [protocol.md](./protocol.md)
11
+ - **AppStructure (앱 구조·권한·모듈)** — 앱 메뉴 트리·화면 권한·기능 모듈 on/off 를 한 배열로 정의하거나, 그 배열에서 평탄 권한 목록을 뽑거나 모듈 활성 여부를 판정할 때. 자세히: [app-structure.md](./app-structure.md)
12
+ - **WebSocket 프로토콜 (메시지·바이너리)** — WebSocket 위 메시지의 인코딩·청킹·재조립을 직접 다루거나(서비스 클라이언트/서버 내부, worker 위임), 메시지 타입·크기 한계를 확인할 때. 자세히: [protocol.md](./protocol.md)
12
13
 
13
14
  ## 이벤트 정의 (defineEvent / ServiceEventDef)
14
15
 
@@ -20,39 +21,40 @@
20
21
  function defineEvent<TInfo = unknown, TData = unknown>(eventName: string): ServiceEventDef<TInfo, TData>
21
22
  ```
22
23
 
23
- - `TInfo` — 구독자가 "무엇을 구독하는지" 식별하는 메타데이터 타입. 발생측 selector 가 이 값을 보고 전달 대상을 골라냄. 예: `{ warehouseId: number }`.
24
- - `TData` — 이벤트가 실어 나르는 페이로드 타입. 구독 콜백이 받는 인자 타입. 예: `{ orderId: number; status: string }`.
25
- - `eventName` — 라우팅 키 문자열. 같은 이름이면 같은 이벤트로 취급되므로 앱 내에서 고유해야 함.
24
+ - `TInfo` (제네릭) — 구독자가 "무엇을 구독하는지" 식별하는 메타데이터 타입. 발생측 selector 가 이 값을 보고 전달 대상을 골라냄. 미지정 시 `unknown`. 예: `{ warehouseId: number }`.
25
+ - `TData` (제네릭) — 이벤트가 실어 나르는 페이로드 타입. 구독 콜백이 받는 인자 타입. 미지정 시 `unknown`. 예: `{ orderId: number; status: string }`.
26
+ - `eventName` (인자) — 라우팅 키 문자열. 같은 이름이면 같은 이벤트로 취급되므로 앱 내에서 고유해야 함.
26
27
  - 반환된 `ServiceEventDef` 를 그대로 발생·구독 API 의 첫 인자로 넘기면 이름·타입이 추론됨.
27
28
 
28
29
  ```ts
29
- // 공통 패키지(@<workspace>/common)에서 정의 + export
30
30
  import { defineEvent } from "@simplysm/service-common";
31
31
 
32
+ // 공통 패키지(@<workspace>/common)에서 정의 + export
32
33
  export const OrderStatusChangedEvent = defineEvent<
33
- { warehouseId: number }, // TInfo
34
- { orderId: number; status: string } // TData
34
+ { warehouseId: number }, // TInfo: 구독·필터 기준 메타데이터
35
+ { orderId: number; status: string } // TData: 전달 페이로드
35
36
  >("OrderStatusChanged");
36
37
  ```
37
38
 
38
- 발생·구독은 `@simplysm/service-client`(`addListener`/`emitEvent`/`getEvent`) 또는 서버 `ctx.server.emitEvent` 로 한다. 메커니즘 전반은 매뉴얼 `manuals/event.md` 참조.
39
+ 발생·구독은 `@simplysm/service-client`(`addListener`/`emitEvent`/`getEvent`) 또는 서버 `ctx.server.emitEvent` 로 한다. 여기서는 정의만 담당한다.
39
40
 
40
41
  ### ServiceEventDef
41
42
 
42
43
  ```ts
43
44
  interface ServiceEventDef<TInfo = unknown, TData = unknown> {
44
45
  eventName: string;
45
- readonly $info: TInfo; // 타입 추출 전용 마커. 런타임 미사용.
46
- readonly $data: TData; // 타입 추출 전용 마커. 런타임 미사용.
46
+ readonly $info: TInfo;
47
+ readonly $data: TData;
47
48
  }
48
49
  ```
49
50
 
50
- - `eventName` — 라우팅 키. `defineEvent` 인자가 그대로 들어감.
51
- - `$info` / `$data` 타입 추론용 phantom 필드. 런타임 값은 `undefined`. 발생·구독 API 가 이 마커로 `TInfo`/`TData` 를 끌어다 씀. 직접 읽지 말 것.
51
+ - `eventName: string` — 라우팅 키. `defineEvent` 인자가 그대로 들어가며 런타임에 실제로 쓰이는 유일한 값.
52
+ - `$info: TInfo` (readonly) `TInfo` 타입 추출 전용 phantom 마커. 런타임 값은 `undefined`(사용되지 않음). 발생·구독 API 가 이 마커로 `TInfo` 를 끌어다 씀.
53
+ - `$data: TData` (readonly) — `TData` 타입 추출 전용 phantom 마커. 런타임 값은 `undefined`(사용되지 않음). 직접 읽지 말 것.
52
54
 
53
- ## 내장 RPC 서비스 인터페이스 (OrmService / AutoUpdateService / DbConnOptions)
55
+ ## 내장 RPC 서비스 시그니처 (OrmService / AutoUpdateService)
54
56
 
55
- 서버가 구현하고 클라이언트가 프록시로 호출하는 내장 서비스의 메서드 계약. 직접 구현하기보다, 시그니처(인자·반환)를 확인할 때 참조. 클라이언트 호출은 `client.getService<...>("...")` 프록시 경유.
57
+ 서버가 구현하고 클라이언트가 프록시로 호출하는 내장 서비스의 메서드 계약. 직접 구현하기보다 시그니처(인자·반환)를 확인할 때 참조. 클라이언트 호출은 `client.getService<...>("...")` 프록시 경유.
56
58
 
57
59
  ### OrmService
58
60
 
@@ -72,14 +74,15 @@ interface OrmService {
72
74
  }
73
75
  ```
74
76
 
75
- - `getInfo(opt)` — 연결 설정의 dialect/database/schema 메타를 조회. `database`/`schema` 는 설정에 따라 없을 수 있어 optional. 연결 없이 설정만 확인할 때.
77
+ - `getInfo(opt)` — 연결 설정의 dialect/database/schema 메타를 조회. `database`/`schema` 는 dialect 따라 없을 수 있어 optional. 연결을 열지 않고 설정 메타만 볼 때.
76
78
  - `connect(opt)` — DB 에 연결하고 커넥션 id(`number`)를 반환. 이후 모든 메서드는 이 id 로 대상 커넥션을 지정.
77
79
  - `close(connId)` — 해당 커넥션을 닫음. 작업 종료 시 호출.
78
80
  - `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` 매핑(타입·인코딩 정보). 대량 적재 경로.
81
+ - `commitTransaction(connId)` — 트랜잭션 확정.
82
+ - `rollbackTransaction(connId)` — 트랜잭션 취소.
83
+ - `executeParametrized(connId, query, params?)` — 파라미터 바인딩 raw SQL 직접 실행. `params` 플레이스홀더에 순서대로 바인딩(없으면 바인딩 없는 쿼리). 결과는 결과셋별 배열(`unknown[][]`).
84
+ - `executeDefs(connId, defs, options?)` — ORM 코어가 만든 `QueryDef` 배열을 일괄 실행. `options[i]` 는 `defs[i]` 결과셋의 컬럼 파싱 메타(`ResultMeta`)로, 메타 불필요한 def 자리는 `undefined`.
85
+ - `bulkInsert(connId, tableName, columnDefs, records)` — 다건 레코드 대량 삽입. `columnDefs` 는 컬럼명→`ColumnMeta`(타입·인코딩) 매핑, `records` 는 삽입할 행 배열.
83
86
 
84
87
  `Dialect`/`IsolationLevel`/`QueryDef`/`ColumnMeta`/`ResultMeta` 는 `@simplysm/orm-common` 타입.
85
88
 
@@ -89,8 +92,8 @@ interface OrmService {
89
92
  type DbConnOptions = { configName?: string; config?: Record<string, unknown> }
90
93
  ```
91
94
 
92
- - `configName` — 서버에 등록된 DB 연결 설정의 이름으로 연결 대상 선택. 등록된 설정을 때. (`getInfo`/`connect` 는 `configName` 필수로 좁혀 받음.)
93
- - `config` — 등록 설정 대신 즉석 연결 설정 객체를 직접 전달할 때.
95
+ - `configName?: string` — 서버에 등록된 DB 연결 설정의 이름. 이름으로 서버가 접속 정보를 찾음. (`getInfo`/`connect` 는 `configName` 필수로 교차해 받음.)
96
+ - `config?: Record<string, unknown>` — 등록 설정 대신 직접 넘기는 즉석 접속 설정 객체. `configName` 으로 가리킬 수 없을 때 사용.
94
97
 
95
98
  ### AutoUpdateService
96
99
 
@@ -102,18 +105,18 @@ interface AutoUpdateService {
102
105
  }
103
106
  ```
104
107
 
105
- - `getLastVersion(platform)` — 지정 플랫폼의 최신 버전·다운로드 경로를 조회. 버전이 없으면 `undefined`(결측 보존). `platform` 예: `"win32"`, `"darwin"`, `"linux"`.
108
+ - `getLastVersion(platform)` — 지정 플랫폼(예: `"win32"`·`"darwin"`·`"linux"`)의 최신 버전·다운로드 경로를 조회. 등록된 버전이 없으면 `undefined`(결측 보존).
106
109
 
107
110
  ## 파일 업로드 결과 (ServiceUploadResult)
108
111
 
109
112
  ```ts
110
113
  interface ServiceUploadResult {
111
- path: string; // 서버 내 저장 경로
112
- filename: string; // 원본 파일명
113
- size: number; // 파일 크기 (바이트)
114
+ path: string;
115
+ filename: string;
116
+ size: number;
114
117
  }
115
118
  ```
116
119
 
117
- - `path` — 업로드된 파일이 서버에 저장된 경로. 업로드 후 그 파일을 참조·연결할 때.
118
- - `filename` — 클라이언트가 올린 원본 파일명. 표시·재다운로드 명칭에 사용.
119
- - `size` — 파일 크기(바이트). 용량 표시·검증에 사용.
120
+ - `path: string` — 업로드된 파일이 서버에 저장된 경로. 업로드 후 그 파일을 참조·연결할 때.
121
+ - `filename: string` — 클라이언트가 올린 원본 파일명. 표시·재다운로드 명칭에 사용.
122
+ - `size: number` — 파일 크기(바이트). 용량 표시·검증에 사용.
@@ -1,6 +1,6 @@
1
1
  # @simplysm/service-common — app-structure
2
2
 
3
- 앱의 메뉴 트리·화면 권한·기능 모듈 on/off 를 한 배열(`AppStructureItem[]`)로 정의하는 타입과, 그 배열에서 평탄 권한 목록을 뽑거나 모듈 활성 여부를 판정하는 유틸. 메뉴를 정의하거나(타입), 권한 페이지·메뉴 필터를 구성할 때(유틸) 같이 읽힌다. 공통 패키지에 클라이언트별 배열 상수를 두고 앱 부트스트랩에서 연결하는 패턴은 매뉴얼 `manuals/client-app-structure.md` 참조.
3
+ 앱의 메뉴 트리·화면 권한·기능 모듈 on/off 를 한 배열(`AppStructureItem[]`)로 정의하는 타입과, 그 배열에서 평탄 권한 목록을 뽑거나 모듈 활성 여부를 판정하는 유틸. 메뉴를 정의할 때(타입) 권한 페이지·메뉴 필터를 구성할 때(유틸) 같이 읽힌다. 공통 패키지에 클라이언트별 배열 상수를 두고 앱 부트스트랩에서 연결하는 패턴은 매뉴얼 `manuals/client-app-structure.md` 참조.
4
4
 
5
5
  ## AppStructureItem
6
6
 
@@ -25,12 +25,12 @@ interface AppStructureGroupItem<TModule> {
25
25
  }
26
26
  ```
27
27
 
28
- - `code` — 항목 코드. 부모부터 dot 으로 이어져 화면을 식별(예: `inventory.goods-inventory`).
29
- - `title` — 메뉴에 표시할 이름.
30
- - `modules` — 표시 조건(OR). 나열한 모듈 중 하나라도 활성(`usableModules`)이면 표시. 없으면 모듈과 무관하게 표시.
31
- - `requiredModules` — 표시 조건(AND). 나열한 모듈이 모두 활성이어야 표시.
32
- - `icon` — 메뉴 아이콘(@ng-icons SVG 문자열 등).
33
- - `children` — 하위 항목 배열. 그룹의 필수 필드(이게 있으면 그룹으로 판별).
28
+ - `code: string` — 항목 코드. 부모부터 dot 으로 이어져 화면을 식별(예: `inventory.goods-inventory`). 라우팅 경로·권한 키의 기준.
29
+ - `title: string` — 메뉴에 표시할 이름.
30
+ - `modules?: TModule[]` — 표시 조건(OR). 나열한 모듈 중 하나라도 활성(`usableModules`)이면 표시. 없으면 모듈과 무관하게 표시.
31
+ - `requiredModules?: TModule[]` — 표시 조건(AND). 나열한 모듈이 모두 활성이어야 표시.
32
+ - `icon?: string` — 메뉴 아이콘(@ng-icons SVG 문자열 등).
33
+ - `children: AppStructureItem<TModule>[]` — 하위 항목 배열. 그룹의 필수 필드(이게 있으면 그룹으로 판별). 표시 가능한 자식이 하나도 없으면 그룹도 메뉴에서 빠짐.
34
34
 
35
35
  ## AppStructureLeafItem
36
36
 
@@ -51,10 +51,10 @@ interface AppStructureLeafItem<TModule> {
51
51
  ```
52
52
 
53
53
  - `code` / `title` / `modules` / `requiredModules` / `icon` — 그룹과 동일 의미.
54
- - `perms: ("use" | "edit")[]` — 이 화면에 부여 가능한 권한 종류. `"use"` = 조회 권한, `"edit"` = 편집 권한. 지정한 화면만 권한 페이지·권한 체크 대상이 됨. 생략하면 제약 없는 화면(항상 모든 권한 활성).
55
- - `subPerms` — 한 화면 안의 세부 기능 권한 목록.
56
- - `url` — 외부 링크 등 이동 경로. 일반 화면 라우팅 대신 외부로 보낼 때.
57
- - `isNotMenu: boolean` — 메뉴 노출 여부 토글. `true` 면 사이드 메뉴에서 숨김(라우팅 대상 화면 자체는 유지). 홈·내정보 등 직접 진입 화면에 사용.
54
+ - `perms?: ("use" | "edit")[]` — 이 화면에 부여 가능한 권한 종류. `"use"` = 조회 권한, `"edit"` = 편집 권한. 지정한 화면만 권한 페이지·권한 체크 대상이 됨. 생략하면 제약 없는 화면(항상 모든 권한 활성).
55
+ - `subPerms?: AppStructureSubPermission<TModule>[]` — 한 화면 안의 세부 기능 권한 목록.
56
+ - `url?: string` — 외부 링크 등 이동 경로. 일반 화면 라우팅 대신 외부로 보낼 때.
57
+ - `isNotMenu?: boolean` — 메뉴 노출 토글. `true` 면 사이드 메뉴에서 숨김(라우팅 대상 화면 자체는 유지). 홈·내정보 등 직접 진입 화면에 사용. 미지정/`false` 면 메뉴에 노출.
58
58
 
59
59
  ## AppStructureSubPermission
60
60
 
@@ -70,14 +70,15 @@ interface AppStructureSubPermission<TModule> {
70
70
  }
71
71
  ```
72
72
 
73
- - `code` — 세부 권한 코드. 화면 코드 뒤에 이어 붙어 권한 키를 이룸(`...code.subCode.perm`).
74
- - `title` — 권한 페이지에 표시할 세부 기능 이름.
75
- - `modules` / `requiredModules` — 세부 권한 자체의 모듈 표시 조건(OR/AND). 모듈 비활성이면 이 세부 권한은 평탄화에서 제외.
76
- - `perms: ("use" | "edit")[]` — 세부 기능에 부여 가능한 권한 종류. leaf 달리 필수.
73
+ - `code: string` — 세부 권한 코드. 화면 코드 뒤에 이어 붙어 권한 키를 이룸(`...code.subCode.perm`).
74
+ - `title: string` — 권한 페이지에 표시할 세부 기능 이름.
75
+ - `modules?: TModule[]` — 세부 권한 자체의 모듈 표시 조건(OR). 모듈 비활성이면 이 세부 권한은 평탄화에서 제외.
76
+ - `requiredModules?: TModule[]` — 세부 권한 자체의 모듈 표시 조건(AND).
77
+ - `perms: ("use" | "edit")[]` — 이 세부 기능에 부여 가능한 권한 종류. `"use"` = 조회, `"edit"` = 편집. leaf 의 `perms` 와 달리 필수.
77
78
 
78
79
  ## FlatPermission
79
80
 
80
- `getFlatPermissions` 가 돌려주는 평탄화된 권한 한 줄. 권한 페이지·권한 매칭에 쓰는 표현.
81
+ `getFlatPermissions` 가 돌려주는 평탄화된 권한 한 줄. 권한 페이지 표시·권한 매칭에 쓰는 표현.
81
82
 
82
83
  ```ts
83
84
  interface FlatPermission<TModule = unknown> {
@@ -87,9 +88,9 @@ interface FlatPermission<TModule = unknown> {
87
88
  }
88
89
  ```
89
90
 
90
- - `titleChain` — 루트부터 이 권한까지의 표시명 경로. 권한 페이지에서 계층 라벨로 사용.
91
- - `codeChain` — 루트부터의 코드 경로 + 마지막에 권한 종류(`"use"`/`"edit"`)·세부코드가 붙은 전체 키. 권한 식별자.
92
- - `modulesChain` — 경로상 각 레벨이 가진 `modules` 배열들의 모음. 이 권한이 어떤 모듈 조건에 묶였는지.
91
+ - `titleChain: string[]` — 루트부터 이 권한까지의 표시명 경로. 권한 페이지에서 계층 라벨로 사용.
92
+ - `codeChain: string[]` — 루트부터의 코드 경로 + 마지막에 권한 종류(`"use"`/`"edit"`)·세부코드가 붙은 전체 키. 권한 식별자.
93
+ - `modulesChain: TModule[][]` — 경로상 각 레벨이 가진 `modules` 배열들의 모음. 이 권한이 어떤 모듈 조건에 묶였는지 표현.
93
94
 
94
95
  ## isUsableModules
95
96
 
@@ -100,9 +101,9 @@ function isUsableModules<TModule>(modules: TModule[] | undefined, requiredModule
100
101
  한 항목의 모듈 조건이 현재 활성 모듈로 충족되는지 판정.
101
102
 
102
103
  - `modules` — OR 조건. 나열 중 하나라도 `usableModules` 에 있으면 통과. `undefined`/빈 배열이면 무조건 통과.
103
- - `requiredModules` — AND 조건. 나열 전부가 `usableModules` 에 있어야 통과. 비어있으면 통과로 간주.
104
+ - `requiredModules` — AND 조건. 나열 전부가 `usableModules` 에 있어야 통과. `undefined`/빈 배열이면 통과로 간주.
104
105
  - `usableModules` — 현재 앱에서 활성인 모듈 목록.
105
- - 반환: requiredModules(AND) 와 modules(OR) 를 모두 만족하면 `true`.
106
+ - 반환: requiredModules(AND)와 modules(OR)를 모두 만족하면 `true`.
106
107
 
107
108
  ## isUsableModulesChain
108
109
 
@@ -128,7 +129,7 @@ function getFlatPermissions<TModule>(items: AppStructureItem<TModule>[], usableM
128
129
  - `items` — 앱 구조 배열(루트).
129
130
  - `usableModules` — 현재 활성 모듈. 모듈 조건을 통과하지 못한 가지(그 자식·권한 포함)는 결과에서 빠짐.
130
131
  - 동작: leaf 의 `perms` 각각, 그리고 `subPerms` 의 각 `perm` 을 한 줄(`FlatPermission`)로 펼침. `subPerms` 는 자체 모듈 조건도 추가로 검사.
131
- - 반환: 표시 가능한 권한들의 평탄 배열. 권한 관리 화면(`<sd-permission-table>` 입력)·권한 매칭 기반.
132
+ - 반환: 표시 가능한 권한들의 평탄 배열. 권한 관리 화면(`<sd-permission-table>` 입력)·권한 매칭의 기반.
132
133
 
133
134
  ```ts
134
135
  import { getFlatPermissions } from "@simplysm/service-common";
@@ -1,6 +1,6 @@
1
1
  # @simplysm/service-common — protocol
2
2
 
3
- WebSocket 위 서비스 메시지의 와이어 포맷. 인코딩·자동 청킹·재조립을 담당하는 코덱(`createServiceProtocol`)과, 클라이언트↔서버가 주고받는 메시지 타입·설정 상수다. 서비스 클라이언트/서버 내부 또는 재조립을 worker 에 위임하는 곳에서 같이 읽힌다.
3
+ WebSocket 위 서비스 메시지의 와이어 포맷. 인코딩·자동 청킹·재조립을 담당하는 코덱(`createServiceProtocol`)과, 클라이언트↔서버가 주고받는 메시지 타입·설정 상수다. 서비스 클라이언트/서버 내부, 또는 재조립을 worker 에 위임하는 곳에서 같이 읽힌다.
4
4
 
5
5
  바이너리 프로토콜 V2: 헤더 28바이트(UUID 16 + TotalSize 8 + Index 4) + JSON 본문. 3MB 초과 시 300KB 청크로 분할, 최대 100MB.
6
6
 
@@ -8,19 +8,19 @@ WebSocket 위 서비스 메시지의 와이어 포맷. 인코딩·자동 청킹
8
8
 
9
9
  ```ts
10
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초
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, // 10초
15
+ EXPIRE_TIME: 60 * 1000, // 60초
16
16
  } as const
17
17
  ```
18
18
 
19
- - `MAX_TOTAL_SIZE` — 단일 메시지 허용 상한(바이트). 초과 시 encode/accumulate 가 throw. 대용량 전송 한계 확인용.
20
- - `SPLIT_MESSAGE_SIZE` — 이 크기 이하면 단일 청크, 초과하면 분할. 청킹 발생 기준.
21
- - `CHUNK_SIZE` — 분할 시 본문 청크 1개 크기.
22
- - `GC_INTERVAL` — 미완성 누적 메시지를 정리하는 타이머 주기.
23
- - `EXPIRE_TIME` — 마지막 청크 이후 이 시간이 지나면 미완성 누적을 폐기.
19
+ - `MAX_TOTAL_SIZE` — 단일 메시지 허용 상한(바이트, 100MB). 초과 시 `encode`/`accumulate` 가 throw. 대용량 전송 한계 확인용.
20
+ - `SPLIT_MESSAGE_SIZE` — 청킹 임계값(3MB). 이 크기 이하면 단일 청크, 초과하면 분할.
21
+ - `CHUNK_SIZE` — 분할 시 본문 청크 1개 크기(300KB).
22
+ - `GC_INTERVAL` — 미완성 누적 메시지를 정리하는 GC 타이머 주기(10초).
23
+ - `EXPIRE_TIME` — 마지막 청크 이후 이 시간(60초)이 지나면 미완성 누적을 폐기.
24
24
 
25
25
  ## createServiceProtocol / ServiceProtocol
26
26
 
@@ -67,7 +67,7 @@ type ServiceAccumulateResult =
67
67
  | { type: "progress"; uuid: string; totalSize: number; completedSize: number };
68
68
  ```
69
69
 
70
- - `type: "complete"` — 모든 청크 수신·재조립 완료. `decode` 파싱된 `message`, `accumulate` 파싱 전 `resultBytes` 를 담음.
70
+ - `type: "complete"` — 모든 청크 수신·재조립 완료. `decode` 결과는 파싱된 `message`, `accumulate` 결과는 파싱 전 `resultBytes` 를 담음.
71
71
  - `type: "progress"` — 일부 청크만 도착. `totalSize`(전체 바이트)·`completedSize`(수신 바이트)로 진행률 산출(예: 진행 콜백).
72
72
  - `uuid` — 어느 메시지의 결과인지 식별. 분기·진행 추적 시 사용.
73
73
 
@@ -84,7 +84,7 @@ type ServiceServerRawMessage = ServiceProgressMessage | ServiceServerMessage
84
84
  ```
85
85
 
86
86
  - `ServiceMessage` — 전체 메시지 유니언.
87
- - `ServiceClientMessage` — 클라이언트가 보내는 메시지(요청·인증·이벤트 등록/해제/조회/발생).
87
+ - `ServiceClientMessage` — 클라이언트가 보내는 메시지(요청·인증·이벤트 등록/제거/조회/발생).
88
88
  - `ServiceServerMessage` — 서버가 보내는 최종 메시지(응답·에러·이벤트 알림).
89
89
  - `ServiceServerRawMessage` — 서버 메시지 + 진행 알림(`progress` 포함). 청크 수신 중 진행 통지를 포함한 서버측 raw 흐름.
90
90
 
@@ -96,9 +96,9 @@ interface ServiceErrorMessage { name: "error"; body: { name: string; message: st
96
96
  interface ServiceAuthMessage { name: "auth"; body: string }
97
97
  ```
98
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` 는 토큰 문자열.
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
102
 
103
103
  ### Service.Method
104
104
 
@@ -107,8 +107,8 @@ interface ServiceRequestMessage { name: `${string}.${string}`; body: unknown[] }
107
107
  interface ServiceResponseMessage { name: "response"; body?: unknown }
108
108
  ```
109
109
 
110
- - `ServiceRequestMessage`(`name: "${service}.${method}"`) — 클라이언트의 서비스 메서드 호출. `name` 은 `서비스.메서드` 형식, `body` 는 인자 배열.
111
- - `ServiceResponseMessage`(`name: "response"`) — 서버의 메서드 응답. `body` 는 결과(없으면 생략).
110
+ - `ServiceRequestMessage` (`name: "${service}.${method}"`) — 클라이언트의 서비스 메서드 호출. `name` 은 `서비스.메서드` 형식 템플릿, `body` 는 인자 배열.
111
+ - `ServiceResponseMessage` (`name: "response"`) — 서버의 메서드 응답. `body` 는 결과(없으면 생략).
112
112
 
113
113
  ### 이벤트
114
114
 
@@ -120,8 +120,8 @@ interface ServiceEmitEventMessage { name: "evt:emit"; body: { keys: string[]; da
120
120
  interface ServiceEventMessage { name: "evt:on"; body: { keys: string[]; data: unknown } }
121
121
  ```
122
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` = 페이로드.
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` = 페이로드.
@@ -1,16 +1,15 @@
1
1
  # @simplysm/service-server
2
2
 
3
- Fastify 기반 RPC 서비스 서버. WebSocket/HTTP 두 전송 계층으로 서비스 메서드를 노출하고, JWT 인증·정적 파일·파일 업로드·서버측 이벤트 브로드캐스팅·내장 ORM/자동업데이트 서비스를 한 프로세스에서 제공한다.
3
+ Fastify 기반 RPC 서비스 서버. WebSocket/HTTP 두 전송으로 서비스 메서드를 노출하고, JWT 인증·정적 파일·파일 업로드·서버측 이벤트 브로드캐스트·내장 ORM/자동업데이트 서비스를 한 프로세스에서 제공한다.
4
4
 
5
5
  ## 사용 트리거 인덱스
6
6
 
7
- - **서버 부트스트랩** (`createServiceServer`, `ServiceServer`, `ServiceServerOptions`) — 서버 앱 진입점에서 옵션을 주고 서버를 띄울 때. 아래 인라인 섹션.
8
- - **서비스 작성** (`defineService`, `auth`, `ServiceContext`, `ServiceMethods` ) — RPC 로 노출할 서비스 메서드를 정의하고 인증·권한을 붙일 때. 자세히: [service-authoring.md](./service-authoring.md)
7
+ - **서버 부트스트랩** (`createServiceServer`, `ServiceServer`, `ServiceServerOptions`, `getEvent`/`emitEvent`) — 서버 앱 진입점에서 옵션을 주고 서버를 띄우거나, 서비스 메서드에서 클라이언트로 이벤트를 발생시킬 때. 아래 인라인 섹션.
8
+ - **서비스 작성** (`defineService`, `auth`, `ServiceContext`, `ServiceMethods`, `ServiceDefinition`, `getServiceAuthPermissions`) — RPC 로 노출할 서비스 메서드를 정의하고 컨텍스트·인증·권한을 붙일 때. 자세히: [service-authoring.md](./service-authoring.md)
9
9
  - **JWT 인증 토큰** (`signJwt`, `verifyJwt`, `decodeJwt`, `AuthTokenPayload`) — 로그인 처리에서 토큰을 서명·검증할 때. 아래 인라인 섹션.
10
- - **서버측 이벤트 발생** (`ServiceServer.emitEvent`, `getEvent`, `ServerEventProxy`) — 서비스 메서드 안에서 구독 클라이언트에 이벤트를 브로드캐스트할 때. 아래 인라인 섹션.
11
- - **내장 서비스** (`OrmService`, `AutoUpdateService`) — DB 접근·자동업데이트를 서버 옵션의 `services` 끼워넣을 때. 아래 인라인 섹션.
12
- - **전송 계층 내부** (`executeServiceMethod`, `createServiceContext`, `ServiceSocket`, `WebSocketHandler`, `ServerProtocolWrapper`, `getConfig` 등) — 커스텀 전송·테스트·디버깅에서 내부 구성요소를 직접 다룰 때. 자세히: [transport-internals.md](./transport-internals.md)
13
- - **V1 레거시 지원** (`handleV1Connection`, `V1RequestHandler` 등) — 구버전(ver=1) 클라이언트의 WebSocket 연결을 받아 자동업데이트만 응대할 때. 자세히: [v1-legacy.md](./v1-legacy.md)
10
+ - **내장 서비스** (`OrmService`, `AutoUpdateService` 및 `*Methods` 타입) — DB 원격 실행·앱 자동업데이트를 서버 옵션 `services` 에 끼워넣을 때. 아래 인라인 섹션.
11
+ - **전송 계층 내부** (`executeServiceMethod`, `createServiceContext`, `ServiceSocket`, `WebSocketHandler`, `ServerProtocolWrapper`, HTTP/정적/업로드 핸들러, `getConfig`) — 커스텀 전송·테스트·디버깅에서 내부 구성요소를 직접 다룰 때. 자세히: [transport-internals.md](./transport-internals.md)
12
+ - **V1 레거시 지원** (`handleV1Connection`, `V1ConnectionOptions`, `V1RequestHandler` 등) — 구버전(ver≠2) 클라이언트의 WebSocket 연결을 받아 자동업데이트만 응대할 때. 자세히: [v1-legacy.md](./v1-legacy.md)
14
13
 
15
14
  ## 서버 부트스트랩
16
15
 
@@ -18,28 +17,28 @@ Fastify 기반 RPC 서비스 서버. WebSocket/HTTP 두 전송 계층으로 서
18
17
 
19
18
  ### createServiceServer / ServiceServer
20
19
 
21
- `createServiceServer<TAuthInfo = unknown>(options: ServiceServerOptions): ServiceServer<TAuthInfo>` — 옵션을 받아 서버 인스턴스 생성(아직 리슨 안 함). `TAuthInfo` 는 인증 토큰의 `data` 페이로드 타입으로, `server.signAuthToken`·`ctx.authInfo` 에 그대로 흐른다. `new ServiceServer(options)` 직접 생성과 동일.
20
+ `createServiceServer<TAuthInfo = unknown>(options: ServiceServerOptions): ServiceServer<TAuthInfo>` — 옵션으로 서버 인스턴스를 생성(아직 리슨 안 함). `new ServiceServer<TAuthInfo>(options)` 직접 생성과 동일. `TAuthInfo` 는 인증 토큰 `data` 페이로드 타입으로 `server.signAuthToken`·`server.verifyAuthToken`·`ctx.authInfo` 에 그대로 흐른다.
22
21
 
23
22
  `ServiceServerOptions` 필드:
24
23
 
25
- - `rootPath: string` — 서버 작업 루트. 정적 파일·업로드·자동업데이트는 모두 `rootPath/www` 하위를, 설정은 `rootPath/.config.json` 을 기준으로 한다. 절대경로를 권장.
26
- - `port: number` — 리슨 포트. `0` 을 주면 OS 가 임의 포트를 할당(테스트용); 실제 포트는 `server.fastify.server.address()` 로 확인.
27
- - `ssl?: { pfxBytes: Uint8Array; passphrase: string }` — HTTPS 인증서. 지정 시 HTTPS 로 기동하고 HSTS·COOP 보안 헤더가 켜진다. 미지정 시 HTTP(평문)로 뜨고 `upgrade-insecure-requests` CSP 가 해제된다. 사내망 평문이면 생략, 외부 노출이면 지정.
28
- - `auth?: { jwtSecret: string } | false` — JWT 인증 설정. 객체면 해당 시크릿으로 토큰 서명·검증; `false` 면 인증을 의도적으로 비활성(권한 요구 메서드도 인증 검사 스킵); `undefined`(미지정)이면서 권한 요구 서비스가 하나라도 있으면 `listen()` 이 에러로 중단. 인증 쓰는 앱이면 객체, 개발·내부 도구로 인증을 끄려면 `false`.
24
+ - `rootPath: string` — 서버 작업 루트. 정적 파일·업로드·자동업데이트는 `rootPath/www` 하위를, 설정은 `rootPath/.config.json` 을 기준으로 한다. 절대경로 권장.
25
+ - `port: number` — 리슨 포트(`host: "0.0.0.0"` 고정). `0` 을 주면 OS 가 임의 포트를 할당하므로 테스트용으로 쓰고, 실제 포트는 `server.fastify.server.address()` 로 확인.
26
+ - `ssl?: { pfxBytes: Uint8Array; passphrase: string }` — HTTPS 인증서. `pfxBytes` = PFX 바이트(내부에서 `Buffer` 로 변환), `passphrase` = PFX 비밀번호. 지정 시 HTTPS 로 기동하고 HSTS·crossOriginOpenerPolicy 보안 헤더가 켜진다. 미지정 시 HTTP(평문)로 뜨고 `upgrade-insecure-requests` CSP 가 해제된다. 사내망 평문이면 생략, 외부 노출이면 지정.
27
+ - `auth?: { jwtSecret: string } | false` — JWT 인증 설정. 객체면 `jwtSecret` 으로 토큰 서명·검증; `false` 면 인증을 의도적으로 비활성(권한 요구 메서드도 인증 검사 스킵); `undefined`(미지정)이면서 권한 요구(`auth(...)` 래핑) 서비스가 하나라도 있으면 `listen()` 이 에러로 중단. 인증 쓰는 앱이면 객체, 인증을 명시적으로 끄려면 `false`.
29
28
  - `services: ServiceDefinition[]` — `defineService` 로 만든 서비스 정의 배열. RPC 로 노출할 서비스 전부를 여기 등록.
30
- - `legacyV1Handlers?: V1RequestHandler[]` — V1(ver=1) 레거시 클라이언트의 커스텀 요청 핸들러. 자세히: [v1-legacy.md](./v1-legacy.md).
29
+ - `legacyV1Handlers?: V1RequestHandler[]` — V1(ver≠2) 레거시 클라이언트의 커스텀 요청 핸들러. 자세히: [v1-legacy.md](./v1-legacy.md).
31
30
 
32
- `ServiceServer` 멤버:
31
+ `ServiceServer<TAuthInfo>` 멤버(`EventEmitter<{ ready: void; close: void }>` 상속):
33
32
 
34
33
  - `readonly options: ServiceServerOptions` — 생성 시 받은 옵션 원본.
35
34
  - `readonly fastify: FastifyInstance` — 내부 Fastify 인스턴스. 포트 조회·추가 라우트 등록 등에 직접 접근.
36
35
  - `isOpen: boolean` — 리슨 성공 후 `true`, `close()` 후 `false`.
37
- - `listen(): Promise<void>` — 플러그인 등록(websocket/helmet/multipart/static/cors) 후 `0.0.0.0:port` 리슨. 완료 시 `"ready"` 이벤트 발생, `SIGINT`/`SIGTERM` 정상 종료 핸들러 등록(10초 내 미종료 시 강제 exit).
36
+ - `listen(): Promise<void>` — 플러그인 등록(websocket/helmet/multipart/static/cors) 후 리슨. 완료 시 `"ready"` 이벤트 발생, `SIGINT`/`SIGTERM` 정상 종료 핸들러 등록(10초 내 미종료 시 강제 `process.exit(1)`). `auth` 미설정인데 권한 요구 서비스가 있으면 시작 전 throw.
38
37
  - `close(): Promise<void>` — 모든 WebSocket 종료 후 Fastify 종료, `"close"` 이벤트 발생.
39
38
  - `signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>` — `auth.jwtSecret` 으로 토큰 서명. 시크릿 미설정 시 throw.
40
39
  - `verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>` — 토큰 검증·디코드. 시크릿 미설정 시 throw.
41
40
  - `getEvent`/`emitEvent` — 아래 "서버측 이벤트 발생" 참조.
42
- - `on("ready" | "close", handler)` — `EventEmitter` 상속. 기동·종료 시점 후킹.
41
+ - `on("ready" | "close", handler)` — 기동·종료 시점 후킹(`EventEmitter` 상속).
43
42
 
44
43
  ```ts
45
44
  const server = createServiceServer<AuthInfo>({
@@ -51,33 +50,15 @@ const server = createServiceServer<AuthInfo>({
51
50
  await server.listen();
52
51
  ```
53
52
 
54
- 주의: `auth` 를 미지정한 채 권한 요구(`auth(...)` 래핑) 서비스를 등록하면 `listen()` 이 즉시 throw 한다. 인증을 끄려면 `auth: false` 를 명시할 것.
53
+ 주의: `auth` 를 미지정한 채 권한 요구 서비스를 등록하면 `listen()` 이 즉시 throw 한다. 인증을 끄려면 `auth: false` 를 명시할 것.
55
54
 
56
- ## JWT 인증 토큰
57
-
58
- 로그인 서비스 메서드에서 자격 확인 후 토큰을 발급하고, 다른 메서드에서 토큰을 검증할 때. 보통은 `server.signAuthToken`/`server.verifyAuthToken`(시크릿 자동 사용)을 쓰고, 시크릿을 직접 다룰 때만 아래 함수를 호출.
59
-
60
- - `AuthTokenPayload<TAuthInfo>` — JWT 페이로드. `jose` 의 `JWTPayload`(`exp`/`iat` 등) 를 확장하며 `roles: string[]`(권한 역할 목록, `auth(["admin"], ...)` 의 권한 매칭 대상)과 `data: TAuthInfo`(앱 정의 사용자 정보, `ctx.authInfo` 로 노출)를 추가.
61
- - `signJwt<TAuthInfo>(jwtSecret: string, payload: AuthTokenPayload<TAuthInfo>): Promise<string>` — HS256 으로 서명. `iat` 자동 설정, 만료 12시간 고정.
62
- - `verifyJwt<TAuthInfo>(jwtSecret: string, token: string): Promise<AuthTokenPayload<TAuthInfo>>` — 서명·만료 검증 후 페이로드 반환. 만료 시 "토큰이 만료되었습니다.", 그 외 검증 실패 시 "유효하지 않은 토큰입니다." 로 throw.
63
- - `decodeJwt<TAuthInfo>(token: string): AuthTokenPayload<TAuthInfo>` — 서명 검증 없이 페이로드만 디코드. 검증이 끝난 토큰의 내용만 다시 읽을 때(만료·위변조 판정에는 쓰지 말 것).
64
-
65
- ```ts
66
- const login = defineService("Auth", (ctx) => ({
67
- login: async (id: string, pw: string) => {
68
- const user = await authenticate(id, pw); // 앱 로직
69
- return ctx.server.signAuthToken({ roles: user.roles, data: user });
70
- },
71
- }));
72
- ```
73
-
74
- ## 서버측 이벤트 발생
55
+ ### 서버측 이벤트 발생
75
56
 
76
57
  서비스 메서드 처리 결과를 구독 중인 클라이언트에 브로드캐스트할 때. 이벤트 정의 객체(`@simplysm/service-common` 의 `defineEvent`)는 클라이언트·서버가 공유한다.
77
58
 
78
- - `ServiceServer.emitEvent<TEventDef>(eventDef: TEventDef, infoSelector: (info: TEventDef["$info"]) => boolean, data: TEventDef["$data"]): Promise<void>` — `eventDef.eventName` 을 구독한 전 클라이언트 리스너 중 `infoSelector(info)` 가 `true` 인 대상에게만 `data` 전송. 전체 전송은 `() => true`, 어느 구독에도 안 걸리면 전송 자체 생략.
79
- - `ServiceServer.getEvent<TEventDef>(eventDef): ServerEventProxy<TEventDef>` — 같은 이벤트를 반복 발생시킬 때 쓰는 프록시. `proxy.emit(infoSelector, data)` 는 `emitEvent` 와 동일.
80
- - `ServerEventProxy<TEventDef>` — `{ emit(infoSelector, data): Promise<void> }` 형태.
59
+ - `emitEvent<TEventDef>(eventDef: TEventDef, infoSelector: (info: TEventDef["$info"]) => boolean, data: TEventDef["$data"]): Promise<void>` — `eventDef.eventName` 을 구독한 전 클라이언트 리스너 중 `infoSelector(info)` 가 `true` 인 대상에게만 `data` 전송. 전체 전송은 `() => true`, 어느 구독에도 안 걸리면 전송 자체가 생략됨.
60
+ - `getEvent<TEventDef>(eventDef): ServerEventProxy<TEventDef>` — 같은 이벤트를 반복 발생시킬 때 쓰는 프록시. `proxy.emit(infoSelector, data)` 는 `emitEvent` 와 동일.
61
+ - `ServerEventProxy<TEventDef>` — `{ emit(infoSelector, data): Promise<void> }` 형태. 구독(리스너 등록)은 클라이언트 전용이라 서버에는 발생 메서드만 있다.
81
62
 
82
63
  ```ts
83
64
  export const OrderService = defineService("Order", (ctx) => ({
@@ -92,15 +73,42 @@ export const OrderService = defineService("Order", (ctx) => ({
92
73
  }));
93
74
  ```
94
75
 
76
+ ## JWT 인증 토큰
77
+
78
+ 로그인 서비스 메서드에서 자격 확인 후 토큰을 발급하고, 다른 메서드에서 토큰을 검증할 때. 보통은 `server.signAuthToken`/`server.verifyAuthToken`(시크릿 자동 사용)을 쓰고, 시크릿을 직접 다룰 때만 아래 함수를 호출.
79
+
80
+ - `AuthTokenPayload<TAuthInfo>` — JWT 페이로드. `jose` 의 `JWTPayload`(`exp`/`iat` 등)를 확장하며 다음을 추가한다.
81
+ - `roles: string[]` — 권한 역할 목록. `auth(["admin"], ...)` 의 권한 매칭 대상.
82
+ - `data: TAuthInfo` — 앱 정의 사용자 정보. `ctx.authInfo` 로 노출.
83
+ - `signJwt<TAuthInfo>(jwtSecret: string, payload: AuthTokenPayload<TAuthInfo>): Promise<string>` — HS256 으로 서명. `iat` 자동 설정, 만료 12시간 고정.
84
+ - `verifyJwt<TAuthInfo>(jwtSecret: string, token: string): Promise<AuthTokenPayload<TAuthInfo>>` — 서명·만료 검증 후 페이로드 반환. 만료 시 "토큰이 만료되었습니다.", 그 외 검증 실패 시 "유효하지 않은 토큰입니다." 로 throw.
85
+ - `decodeJwt<TAuthInfo>(token: string): AuthTokenPayload<TAuthInfo>` — 서명 검증 없이 페이로드만 디코드(동기). 검증이 끝난 토큰의 내용만 다시 읽을 때 사용하고, 만료·위변조 판정에는 쓰지 말 것.
86
+
87
+ ```ts
88
+ const AuthService = defineService("Auth", (ctx) => ({
89
+ login: async (id: string, pw: string) => {
90
+ const user = await authenticate(id, pw); // 앱 로직
91
+ return ctx.server.signAuthToken({ roles: user.roles, data: user });
92
+ },
93
+ }));
94
+ ```
95
+
95
96
  ## 내장 서비스
96
97
 
97
- 서버 옵션의 `services` 배열에 그대로 추가해 사용하는 미리 정의된 서비스. 클라이언트 타입 공유용 `*Type` 함께 export 된다.
98
+ 서버 옵션 `services` 배열에 그대로 추가해 쓰는 미리 정의된 서비스. 두 이름(별칭)으로 노출되며, 클라이언트 타입 공유용 `*Methods` 타입도 함께 export 된다.
98
99
 
99
- - `OrmService` / `OrmServiceType` — `["Orm", "SdOrmService"]` 두 이름으로 노출되는 ORM 서비스. **WebSocket 전송 전용**(소켓 단위로 DB 커넥션을 풀링하므로 HTTP 호출 시 throw). 모든 메서드가 로그인 필요(`auth(...)` 래핑). DB 접속 정보는 `getConfig("orm")[configName]` 으로 `rootPath/.config.json` 의 `orm` 섹션에서 읽는다. 메서드: `getInfo`(dialect/database/schema 조회), `connect`(커넥션 풀에 연결 추가 후 connId 반환), `close`/`beginTransaction`/`commitTransaction`/`rollbackTransaction`(connId 대상 트랜잭션 제어), `executeParametrized`(파라미터 쿼리 실행), `executeDefs`(QueryDef 배열을 dialect 에 맞춰 빌드·실행·파싱), `bulkInsert`(대량 삽입). 소켓 종료 시 해당 소켓의 모든 커넥션 자동 정리.
100
- - `AutoUpdateService` / `AutoUpdateServiceType` `["AutoUpdate", "SdAutoUpdateService"]` 두 이름으로 노출되는 자동업데이트 서비스. 메서드 `getLastVersion(platform: string)``rootPath/www/<clientName>/<platform>/updates` 에서 `android` `.apk`, 그 외면 `.exe` 중 semver 최대 버전을 찾아 `{ version, downloadPath }` 반환(없으면 `undefined`). 인증 불필요.
100
+ - `OrmService` / `OrmServiceMethods` — `["Orm", "SdOrmService"]` 두 이름으로 노출. 전체가 `auth(...)` 래핑이라 로그인 필요하고, **WebSocket 전송 전용**(소켓 단위로 DB 커넥션을 풀링하므로 HTTP 호출 시 throw). DB 접속 정보는 `ctx.getConfig("orm")[configName]` 으로 `rootPath/.config.json` 의 `orm` 섹션에서 읽는다. 소켓이 닫히면 소켓의 모든 커넥션을 자동 정리. 메서드:
101
+ - `getInfo(opt)` — dialect·database·schema 조회(`mssql-azure` `mssql` 정규화).
102
+ - `connect(opt)` — 새 DB 연결을 풀에 추가하고 정수 `connId` 반환.
103
+ - `close(connId)` — 해당 연결 종료.
104
+ - `beginTransaction(connId, isolationLevel?)` / `commitTransaction(connId)` / `rollbackTransaction(connId)` — connId 대상 트랜잭션 제어.
105
+ - `executeParametrized(connId, query, params?)` — 파라미터 바인딩 SQL 실행.
106
+ - `executeDefs(connId, defs, options?)` — `QueryDef[]` 를 dialect 에 맞춰 빌드·실행하고, `options[i]` 가 있으면 결과를 파싱·반환(전부 `null` 이면 일괄 실행만).
107
+ - `bulkInsert(connId, tableName, columnDefs, records)` — 대량 삽입.
108
+ - `AutoUpdateService` / `AutoUpdateServiceMethods` — `["AutoUpdate", "SdAutoUpdateService"]` 두 이름으로 노출. 인증 불필요. 메서드 `getLastVersion(platform: string)` 은 `rootPath/www/<clientName>/<platform>/updates` 에서 `android` 면 `.apk`, 그 외면 `.exe` 파일 중 semver 최대 버전을 찾아 `{ version, downloadPath }` 반환(대상 없으면 `undefined`).
101
109
 
102
110
  ```ts
103
111
  services: [OrmService, AutoUpdateService, ...앱서비스들]
104
112
  ```
105
113
 
106
- 주의: 두 내장 서비스는 클라이언트가 `getService("Orm")` / `getService("AutoUpdate")` 짧은 이름 또는 `SdOrmService`/`SdAutoUpdateService` 레거시 이름 어느 쪽으로도 호출할 수 있다.
114
+ 주의: 두 내장 서비스는 클라이언트가 짧은 이름(`getService("Orm")`/`getService("AutoUpdate")`) 또는 레거시 이름(`SdOrmService`/`SdAutoUpdateService`) 어느 쪽으로도 호출할 수 있다.
@@ -51,11 +51,11 @@ export const AdminService = defineService("Admin", auth((ctx) => ({
51
51
 
52
52
  - `server: ServiceServer<TAuthInfo>` — 서버 인스턴스. `ctx.server.emitEvent(...)` 로 이벤트 발생, `ctx.server.signAuthToken(...)` 로 토큰 발급.
53
53
  - `socket?: ServiceSocket` — WebSocket 요청일 때만 존재하는 소켓. HTTP 요청이면 `undefined`(소켓 필요한 기능은 존재 검사 필수).
54
- - `http?: { clientName: string; authTokenPayload? }` — HTTP 요청일 때만 존재.
55
- - `legacy?: { clientName? }` — V1 레거시 연결 컨텍스트(자동업데이트 전용).
56
- - `get authInfo: TAuthInfo | undefined` — 검증된 토큰의 `data` 페이로드. 비로그인 요청이면 `undefined`(결측을 그대로 노출하므로 받는 쪽도 옵셔널로 다룰 것).
57
- - `get clientName: string | undefined` — 요청 클라이언트 이름(소켓→HTTP→레거시 순 우선). 빈 문자열·`..`·슬래시(`/`,`\`) 포함 등 경로 탈출 위험 값이면 throw.
58
- - `get clientPath: string | undefined` — `rootPath/www/<clientName>` 절대경로. clientName 없으면 `undefined`.
54
+ - `http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }` — HTTP 요청일 때만 존재.
55
+ - `legacy?: { clientName?: string }` — V1 레거시 연결 컨텍스트(자동업데이트 전용).
56
+ - `get authInfo(): TAuthInfo | undefined` — 검증된 토큰의 `data` 페이로드(소켓→HTTP 순). 비로그인 요청이면 `undefined`(결측을 그대로 노출하므로 받는 쪽도 옵셔널로 다룰 것).
57
+ - `get clientName(): string | undefined` — 요청 클라이언트 이름(소켓→HTTP→레거시 순 우선). 빈 문자열·`..`·슬래시(`/`,`\`) 포함 등 경로 탈출 위험 값이면 throw.
58
+ - `get clientPath(): string | undefined` — `rootPath/www/<clientName>` 절대경로. clientName 없으면 `undefined`.
59
59
  - `getConfig<T>(section: string): Promise<T>` — `rootPath/.config.json` 루트 설정에 클라이언트별 `www/<clientName>/.config.json` 을 머지한 뒤 `section` 키 값을 반환. 섹션이 없으면 throw. 설정 파일은 변경 시 자동 리로드(파일 워처 + 캐시).
60
60
 
61
61
  ```ts
@@ -67,7 +67,7 @@ export const ReportService = defineService("Report", auth((ctx) => ({
67
67
 
68
68
  ## ServiceDefinition / ServiceMethods / getServiceAuthPermissions
69
69
 
70
- - `ServiceDefinition<TMethods>` — `defineService` 반환 타입. `{ name: string; names: string[]; factory: (ctx) => TMethods; authPermissions?: string[] }`. `names` 는 별칭 전체, `authPermissions` 는 서비스 수준 `auth` 권한(없으면 `undefined`).
70
+ - `ServiceDefinition<TMethods>` — `defineService` 반환 타입. `{ name: string; names: string[]; factory: (ctx) => TMethods; authPermissions?: string[] }`. `name` 은 대표 이름, `names` 는 별칭 전체, `authPermissions` 는 서비스 수준 `auth` 권한(없으면 `undefined`).
71
71
  - `type ServiceMethods<TDefinition>` — `ServiceDefinition<M>` 에서 메서드 시그니처 `M` 만 추출하는 타입 유틸. 클라이언트와 서비스 타입을 공유하려고 common 패키지에 `export type XxxServiceMethods = ServiceMethods<typeof XxxService>` 로 재노출하고, 클라이언트는 `client.getService<XxxServiceMethods>("Xxx")` 로 사용.
72
72
  - `getServiceAuthPermissions(fn: Function): string[] | undefined` — `auth(...)` 로 래핑된 함수에서 권한 배열을 읽음. 래핑 안 됐으면 `undefined`. 내부 실행기·커스텀 전송에서만 필요(일반 작성에서는 불필요).
73
73