@simplysm/sd-claude 14.0.76 → 14.0.78
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/output-styles/sd-tone.md +128 -0
- package/claude/references/sd-simplysm14/apis/angular/README.md +28 -89
- package/claude/references/sd-simplysm14/apis/angular/app-structure.md +75 -32
- package/claude/references/sd-simplysm14/apis/angular/buttons.md +65 -29
- package/claude/references/sd-simplysm14/apis/angular/crud.md +86 -21
- package/claude/references/sd-simplysm14/apis/angular/forms.md +168 -42
- package/claude/references/sd-simplysm14/apis/angular/infrastructure.md +200 -49
- package/claude/references/sd-simplysm14/apis/angular/kanban.md +64 -20
- package/claude/references/sd-simplysm14/apis/angular/layout.md +75 -30
- package/claude/references/sd-simplysm14/apis/angular/modal.md +92 -40
- package/claude/references/sd-simplysm14/apis/angular/routing.md +86 -25
- package/claude/references/sd-simplysm14/apis/angular/selection-managers.md +72 -41
- package/claude/references/sd-simplysm14/apis/angular/shared-data.md +113 -21
- package/claude/references/sd-simplysm14/apis/angular/sheet.md +108 -33
- package/claude/references/sd-simplysm14/apis/angular/toast.md +81 -30
- package/claude/references/sd-simplysm14/apis/angular/visual.md +140 -32
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-auto-update/README.md +46 -43
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-intent/README.md +59 -48
- package/claude/references/sd-simplysm14/apis/capacitor-plugin-usb-storage/README.md +17 -7
- package/claude/references/sd-simplysm14/apis/core-common/README.md +43 -116
- package/claude/references/sd-simplysm14/apis/core-common/extensions.md +74 -109
- package/claude/references/sd-simplysm14/apis/core-common/features.md +40 -35
- package/claude/references/sd-simplysm14/apis/core-common/types.md +80 -106
- package/claude/references/sd-simplysm14/apis/core-common/utils.md +142 -111
- package/claude/references/sd-simplysm14/apis/core-node/README.md +7 -16
- package/claude/references/sd-simplysm14/apis/core-node/consola.md +33 -38
- package/claude/references/sd-simplysm14/apis/core-node/cpx.md +25 -33
- package/claude/references/sd-simplysm14/apis/core-node/fs-watcher.md +27 -38
- package/claude/references/sd-simplysm14/apis/core-node/fsx.md +32 -60
- package/claude/references/sd-simplysm14/apis/core-node/pathx.md +14 -45
- package/claude/references/sd-simplysm14/apis/core-node/worker.md +35 -81
- package/claude/references/sd-simplysm14/apis/excel/README.md +178 -80
- package/claude/references/sd-simplysm14/apis/lint/README.md +5 -0
- package/claude/references/sd-simplysm14/apis/orm-node/README.md +1 -1
- package/claude/references/sd-simplysm14/apis/sd-claude/README.md +28 -5
- package/claude/references/sd-simplysm14/apis/sd-cli/README.md +1 -1
- package/claude/references/sd-simplysm14/apis/service-client/README.md +57 -50
- package/claude/references/sd-simplysm14/apis/service-server/README.md +8 -15
- package/claude/references/sd-simplysm14/apis/service-server/auth.md +24 -16
- package/claude/references/sd-simplysm14/apis/service-server/builtin-services.md +55 -31
- package/claude/references/sd-simplysm14/apis/service-server/define-service.md +28 -44
- package/claude/references/sd-simplysm14/apis/service-server/internals.md +59 -18
- package/claude/references/sd-simplysm14/apis/service-server/server.md +37 -46
- package/claude/references/sd-simplysm14/manuals/client-component.md +3 -1
- package/claude/references/sd-simplysm14/manuals/logging.md +9 -8
- package/claude/rules/sd-base-rules.md +377 -219
- package/claude/settings.json +1 -0
- package/claude/skills/sd-commit/SKILL.md +31 -8
- package/claude/skills/sd-docs/SKILL.md +15 -10
- package/claude/skills/sd-docs/references/subagent-prompt.md +26 -8
- package/claude/skills/sd-impl/SKILL.md +1 -1
- package/claude/skills/sd-skill/references/skill-authoring.md +1 -1
- package/claude/skills/sd-spec/SKILL.md +22 -13
- package/claude/skills/sd-spec/references/spec-authoring.md +1 -1
- package/claude/skills/sd-unpack/SKILL.md +150 -26
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/_common.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/eml_handler.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/office_com.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/__pycache__/pdf_handler.cpython-314.pyc +0 -0
- package/claude/skills/sd-unpack/scripts/handlers/_common.py +17 -2
- package/claude/skills/sd-unpack/scripts/handlers/eml_handler.py +100 -24
- package/claude/skills/sd-unpack/scripts/handlers/msg_handler.py +140 -27
- package/claude/skills/sd-unpack/scripts/handlers/office_com.py +698 -107
- package/claude/skills/sd-unpack/scripts/handlers/office_worker.py +34 -26
- package/claude/skills/sd-unpack/scripts/handlers/pdf_handler.py +130 -8
- package/package.json +1 -1
|
@@ -66,4 +66,4 @@ dialect 분기 union. 공통 필드: `host`, `port?`, `username`, `password`, `d
|
|
|
66
66
|
new NodeDbContextExecutor(config: DbConnConfig)
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
`DbContextExecutor` 구현. `connect` 시 내부에서 `createDbConn` + `conn.connect()` 수행. `executeDefs(defs, resultMetas?)` 는 `@simplysm/orm-common` 의 `createQueryBuilder` + `parseQueryResult` 를 사용해 `QueryDef[]` → SQL → 파싱 결과. `resultMetas` 가 전부 `null` 인 경우 단일 결합 SQL 1회로 묶어
|
|
69
|
+
`DbContextExecutor` 구현. `connect` 시 내부에서 `createDbConn` + `conn.connect()` 수행. `executeDefs(defs, resultMetas?)` 는 `@simplysm/orm-common` 의 `createQueryBuilder` + `parseQueryResult` 를 사용해 `QueryDef[]` → SQL → 파싱 결과. `resultMetas` 가 전부 `null` 인 경우 단일 결합 SQL 1회로 묶어 실행하고 `defs.length` 개의 빈 배열 반환(결과 불필요 케이스 최적화). 미연결 상태에서 메서드 호출 시 `DB_CONN_ERRORS.NOT_CONNECTED` SdError throw.
|
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
# @simplysm/sd-claude
|
|
2
2
|
|
|
3
|
-
Claude Code 셋업 자산(`.claude/` 의 sd-* 스킬·룰·훅) 배포 및 `sd-claude` CLI 제공 패키지.
|
|
3
|
+
Claude Code 셋업 자산(`.claude/` 의 sd-* 스킬·룰·훅) 배포 및 `sd-claude` CLI 제공 패키지. 코드 API 없음 — 외부 import 가능한 라이브러리 심볼 미노출.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## 사용 트리거 인덱스
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
7
|
+
- 현재 로그인된 Claude 계정을 프로필로 저장 → [auth save](#cli-sd-claude)
|
|
8
|
+
- 저장된 다른 Claude 계정으로 전환 → [auth switch](#cli-sd-claude)
|
|
9
|
+
- 소비 프로젝트 설치 시 `.claude/` 자산 자동 배치 → [postinstall](#패키지-훅)
|
|
10
|
+
- 워크스페이스 `.claude/` 변경분을 패키지 `claude/` 로 동기화(배포 직전) → [prepack / sync](#패키지-훅)
|
|
11
|
+
|
|
12
|
+
## CLI `sd-claude`
|
|
13
|
+
|
|
14
|
+
`bin: sd-claude` → `scripts/cli.mjs`. 사용법: `sd-claude <subcommand> <action>`.
|
|
15
|
+
|
|
16
|
+
- `auth save`: 현재 `claude auth status` 의 `orgName` 을 프로필 키로, `~/.claude/.credentials.json` 의 `claudeAiOauth.refreshToken` 과 `~/.claude/statusline-cache.json` 의 사용량 스냅샷을 `~/.claude/profiles.json` 에 저장하고 `current` 를 해당 프로필로 설정. orgName 또는 refreshToken 미획득 시 stderr 출력 후 exit 1.
|
|
17
|
+
- `auth switch`: `profiles.json` 의 계정 목록을 번호 + 현재 마커(`*`) + 사용량(현재 계정은 live, 그 외는 저장 시점) 으로 출력. 번호 입력으로 대상 선택 → 현재 계정의 최신 refreshToken·사용량을 백업 저장 → `CLAUDE_CODE_OAUTH_REFRESH_TOKEN`/`CLAUDE_CODE_OAUTH_SCOPES` 환경변수로 `claude auth login` 을 spawn → 갱신된 refreshToken 저장 + `current` 업데이트. TTY 아니면 exit 1.
|
|
18
|
+
- 그 외 인자: 사용법 출력 후 exit 1.
|
|
19
|
+
|
|
20
|
+
저장 위치 식별자 풀이:
|
|
21
|
+
- `~/.claude/profiles.json`: `{ current: string, accounts: { [orgName]: { refreshToken, usage } } }` 구조. 패키지가 관리.
|
|
22
|
+
- `~/.claude/.credentials.json`: Claude Code 본체가 관리. `claudeAiOauth.refreshToken` 만 읽음.
|
|
23
|
+
- `~/.claude/statusline-cache.json`: Claude Code 본체가 관리. `rate_limits.five_hour` / `seven_day` 의 `used_percentage` + `resets_at` 만 읽음.
|
|
24
|
+
|
|
25
|
+
## 패키지 훅
|
|
26
|
+
|
|
27
|
+
- `postinstall` (`scripts/postinstall.mjs`): 소비 프로젝트의 `node_modules` 설치 시 자동 실행. `INIT_CWD` 또는 `node_modules` 경로 역추적으로 프로젝트 루트 결정 → `<projectRoot>/.claude/` 의 기존 sd-* 항목(root 1단계 + 하위 디렉토리 1단계의 `^sd[-_]` 매칭) 정리 후 패키지 `claude/` 의 동일 항목 + `settings.json` + `simplysm.json` 을 복사. 소비 프로젝트가 `name: "simplysm"` 이고 메이저 버전이 같으면 skip(모노레포 자기 자신 보호). 실패해도 throw 하지 않음(install 차단 방지).
|
|
28
|
+
- `prepack` (`scripts/sync.mjs`): `pnpm pub`/`npm pack` 직전 실행. 워크스페이스 루트(`../../`)의 `.claude/` 에서 sd-* 항목(+ `settings.json`/`simplysm.json`) 을 패키지 `claude/` 로 증분 복사. 동일 파일은 mtime+size 비교로 skip, 변경분은 unlink 후 copy + src mtime 보존, src 에 없는 dest 항목은 prune. 제외: `evals/` 하위, 파일명 `SKILL.eval.md`, `eval_*` 접두 파일. (Windows EPERM 회피 위해 일괄 rmSync 미사용.)
|
|
29
|
+
|
|
30
|
+
식별자 풀이:
|
|
31
|
+
- sd-* 항목 스캔 범위: 디렉토리 root + 1단계 하위. 정규식 `^sd[-_]`. 즉 `.claude/skills/sd-foo/`, `.claude/rules/sd-bar.md`, `.claude/sd-baz/` 등 매칭.
|
|
32
|
+
- watch 훅 연동: 모노레포에서는 `sd.config.ts` 의 `scripts` 타겟이 `.claude/sd-*` 변경 감지 시 본 `sync.mjs` 를 호출(패키지 외부 설정).
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
- **`init`** — 인터랙티브 프롬프트로 SI 워크스페이스 골격 생성.
|
|
17
17
|
|
|
18
18
|
공통 옵션:
|
|
19
|
-
- `-t / --target <pkg>` (반복 가능) — 대상 패키지. `sd.config.ts.packages` 키 (= `@simplysm/` 접두사 **제외**한 짧은 이름). 미지정 시 전체.
|
|
19
|
+
- `-t / --target <pkg>` (반복 가능) — 대상 패키지. `sd.config.ts.packages` 키 (= `@simplysm/` 접두사 **제외**한 짧은 이름). 미지정 시 전체. `check`/`watch`/`dev`/`build`/`publish` 에서 지원. `device` 는 단수(`-t <pkg>` 1회). `replace-deps`/`init` 미지원.
|
|
20
20
|
- `-o / --opt <val>` (반복 가능) — `sd.config.ts` 함수의 `params.opt[]` 로 전달. `check` 와 `init` 제외 전 커맨드에서 지원.
|
|
21
21
|
- `--debug` — 디버그 로그 출력 (모든 커맨드).
|
|
22
22
|
- `--help / -h` — 단독 호출 시 모든 서브커맨드 종합 도움말.
|
|
@@ -1,49 +1,52 @@
|
|
|
1
1
|
# @simplysm/service-client
|
|
2
2
|
|
|
3
|
-
`@simplysm/service-server` 와 WebSocket 으로 통신하는 클라이언트. RPC
|
|
3
|
+
`@simplysm/service-server` 와 WebSocket(`/ws`) 으로 통신하는 클라이언트. RPC 호출·서버 push 이벤트 구독·파일 업/다운로드·원격 ORM 트랜잭션 실행을 단일 `ServiceClient` 에서 제공한다 (Node/브라우저 공용).
|
|
4
4
|
|
|
5
5
|
## 사용 트리거 인덱스
|
|
6
6
|
|
|
7
|
-
- **createServiceClient / ServiceConnectionOptions** — 서버
|
|
8
|
-
- **getService / send / ServiceProxy** —
|
|
9
|
-
- **auth** — 서버 인증 토큰을 등록하고 재연결 시 자동
|
|
10
|
-
- **getEvent / addListener / removeListener / emitEvent** — 서버 push 이벤트를
|
|
7
|
+
- **createServiceClient / ServiceClient / ServiceConnectionOptions** — 서버 접속 클라이언트를 생성하고 `connect()` / `close()` 로 연결 수명을 관리할 때.
|
|
8
|
+
- **getService / send / ServiceProxy** — 서버 등록 서비스의 메서드를 타입 안전 RPC 로 호출할 때 (Proxy 호출 또는 저수준 `send`).
|
|
9
|
+
- **auth** — 서버 인증 토큰을 등록하고 재연결 시 자동 재인증 + 파일 업로드 인증을 활성화할 때.
|
|
10
|
+
- **getEvent / addListener / removeListener / emitEvent / ClientEventProxy** — 서버 push 이벤트를 구독·해지하거나 다른 클라이언트에게 발행할 때.
|
|
11
11
|
- **uploadFile / downloadFileBuffer** — 서버에 multipart 파일을 올리거나 정적 경로에서 바이트(`Uint8Array`)로 받을 때.
|
|
12
|
-
- **createOrmClientConnector / OrmConnectOptions** — 서버
|
|
13
|
-
- **
|
|
14
|
-
- **
|
|
12
|
+
- **createOrmClientConnector / OrmClientConnector / OrmConnectOptions** — 서버 `"Orm"` 서비스를 통해 `DbContext` 트랜잭션을 원격 실행할 때.
|
|
13
|
+
- **OrmClientDbContextExecutor** — `DbContext` 를 직접 구성해야 할 때만. 보통 `OrmClientConnector` 가 내부에서 사용함.
|
|
14
|
+
- **ServiceClient 인스턴스 이벤트 (`state` / `request-progress` / `response-progress` / `server-progress`)** — 연결 상태 변화와 전송/응답/서버 진행률을 인스턴스 레벨에서 구독할 때.
|
|
15
|
+
- **BlobInput / FileCollection / BrowserWorker / isBrowserWorkerSupported / isNodeWorkerSupported / isWorkerSupported** — Node/브라우저 공용 코드에서 DOM 의존 타입을 대체하거나 Worker 오프로딩 가용성을 확인할 때.
|
|
15
16
|
|
|
16
|
-
##
|
|
17
|
+
## 연결 / 생성
|
|
17
18
|
|
|
18
19
|
```ts
|
|
19
20
|
createServiceClient(name: string, options: ServiceConnectionOptions): ServiceClient
|
|
20
21
|
interface ServiceConnectionOptions { port: number; host: string; ssl?: boolean; maxReconnectCount?: number; }
|
|
21
22
|
client.connect(): Promise<void>
|
|
22
|
-
client.close(): Promise<void>
|
|
23
|
+
client.close(): Promise<void> // protocol wrapper 의 worker·resolver 자원도 dispose
|
|
23
24
|
client.connected: boolean
|
|
24
|
-
client.hostUrl: string
|
|
25
|
+
client.hostUrl: string // `${ssl?https:http}://host:port`
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
- WebSocket URL
|
|
28
|
-
- `maxReconnectCount` 기본 10
|
|
29
|
-
- 5s 마다 ping, 30s 무응답 시 강제 재연결. 재연결 성공 시 인증
|
|
28
|
+
- WebSocket URL: `${ws|wss}://host:port/ws?ver=2&clientId=<uuid>&clientName=<name>`.
|
|
29
|
+
- `maxReconnectCount` 기본 10. `0` 이면 재연결 비활성.
|
|
30
|
+
- 5s 마다 ping(`0x01`), 30s 무응답 시 강제 재연결. 재연결 성공 시 인증 토큰과 이벤트 리스너가 자동 복구된다.
|
|
31
|
+
- Node 환경에서 글로벌 `WebSocket` 이 없으면 `ws` 패키지로 polyfill (모듈 로드 시 1회).
|
|
30
32
|
|
|
31
33
|
## RPC 호출
|
|
32
34
|
|
|
33
35
|
```ts
|
|
34
|
-
client.getService<TService>(serviceName): ServiceProxy<TService>
|
|
35
|
-
client.send(serviceName, methodName, params, progress
|
|
36
|
-
type ServiceProxy<T> = { [K in keyof T]: T[K] extends (...a:infer P)=>infer R ? (...a:P)=>Promise<Awaited<R>> : never }
|
|
37
|
-
interface ServiceProgress { request?(s); response?(s); server?(s)
|
|
38
|
-
interface ServiceProgressState { uuid: string; totalSize: number; completedSize: number
|
|
36
|
+
client.getService<TService>(serviceName: string): ServiceProxy<TService>
|
|
37
|
+
client.send(serviceName, methodName, params: unknown[], progress?: ServiceProgress): Promise<unknown>
|
|
38
|
+
type ServiceProxy<T> = { [K in keyof T]: T[K] extends (...a: infer P) => infer R ? (...a: P) => Promise<Awaited<R>> : never }
|
|
39
|
+
interface ServiceProgress { request?(s: ServiceProgressState): void; response?(s): void; server?(s): void }
|
|
40
|
+
interface ServiceProgressState { uuid: string; totalSize: number; completedSize: number }
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
- `getService` 는 Proxy
|
|
43
|
+
- `getService` 는 Proxy. 임의 메서드명 호출 가능하며 타입 보장은 `TService` 로만.
|
|
42
44
|
- 와이어 메시지 이름은 `"<serviceName>.<methodName>"`.
|
|
43
|
-
-
|
|
44
|
-
- **인코드**: body 가 `Uint8Array`, 30KB 초과 문자열, 길이 100 초과 배열, 또는 첫
|
|
45
|
-
- **디코드**: 수신
|
|
46
|
-
- Worker
|
|
45
|
+
- 인코드/디코드의 Worker 오프로딩 임계값:
|
|
46
|
+
- **인코드**: body 가 `Uint8Array`, 30KB 초과 문자열, 길이 100 초과 배열, 또는 첫 원소가 `Uint8Array` 인 배열일 때 Worker.
|
|
47
|
+
- **디코드**: 수신 바이트 30KB 초과 시 Worker.
|
|
48
|
+
- Worker 미지원/초기화 실패 환경은 메인 스레드 fallback.
|
|
49
|
+
- 소켓 끊김·재연결 시 대기 중인 모든 요청은 `"요청 취소됨: 소켓 연결이 끊어졌습니다"` 로 reject.
|
|
47
50
|
|
|
48
51
|
## 인증
|
|
49
52
|
|
|
@@ -51,22 +54,23 @@ interface ServiceProgressState { uuid: string; totalSize: number; completedSize:
|
|
|
51
54
|
client.auth(token: string): Promise<void>
|
|
52
55
|
```
|
|
53
56
|
|
|
54
|
-
- 서버에 `auth` 메시지를 보내고 토큰을 내부 저장. 재연결 시
|
|
55
|
-
- `uploadFile` 은
|
|
57
|
+
- 서버에 `auth` 메시지를 보내고 토큰을 내부 저장. 재연결 시 동일 토큰으로 자동 재인증.
|
|
58
|
+
- `uploadFile` 은 토큰이 없으면 즉시 throw.
|
|
56
59
|
|
|
57
60
|
## 이벤트 (서버 push)
|
|
58
61
|
|
|
59
62
|
```ts
|
|
60
|
-
client.getEvent<TEventDef>(eventName): ClientEventProxy<TEventDef>
|
|
61
|
-
client.addListener<TEventDef>(eventName, info, cb): Promise<string> // key
|
|
62
|
-
client.removeListener(key): Promise<void>
|
|
63
|
-
client.emitEvent<TEventDef>(eventName, infoSelector, data): Promise<void>
|
|
64
|
-
interface ClientEventProxy<T> { addListener(info, cb): Promise<string>; removeListener(key): Promise<void>; emit(infoSelector, data): Promise<void
|
|
63
|
+
client.getEvent<TEventDef>(eventName: string): ClientEventProxy<TEventDef>
|
|
64
|
+
client.addListener<TEventDef>(eventName, info: TEventDef["$info"], cb): Promise<string> // listener key
|
|
65
|
+
client.removeListener(key: string): Promise<void>
|
|
66
|
+
client.emitEvent<TEventDef>(eventName, infoSelector: (info) => boolean, data): Promise<void>
|
|
67
|
+
interface ClientEventProxy<T> { addListener(info, cb): Promise<string>; removeListener(key): Promise<void>; emit(infoSelector, data): Promise<void> }
|
|
65
68
|
```
|
|
66
69
|
|
|
67
|
-
- `addListener` 는
|
|
68
|
-
- `emitEvent` 는 서버에 `evt:gets` 로
|
|
69
|
-
- 핸들러 내부 에러는
|
|
70
|
+
- `addListener` 는 `connected` 상태에서만 호출 가능 (그 외 throw). 로컬 맵에 보관해 재연결 시 자동 재구독.
|
|
71
|
+
- `emitEvent` 는 서버에 `evt:gets` 로 후보 목록을 받아 `infoSelector` 통과분에만 `evt:emit` 호출. 대상 0건이면 emit 생략.
|
|
72
|
+
- 핸들러 내부 에러는 로깅만, 다른 핸들러 호출은 계속.
|
|
73
|
+
- `removeListener` 는 서버 전송 실패를 무시함 (서버가 연결 끊김 시 자동 정리하므로).
|
|
70
74
|
|
|
71
75
|
## 파일 업/다운로드
|
|
72
76
|
|
|
@@ -74,11 +78,11 @@ interface ClientEventProxy<T> { addListener(info, cb): Promise<string>; removeLi
|
|
|
74
78
|
client.uploadFile(files: File[] | FileCollection | { name: string; data: BlobInput }[]): Promise<ServiceUploadResult[]>
|
|
75
79
|
client.downloadFileBuffer(relPath: string): Promise<Uint8Array>
|
|
76
80
|
type BlobInput = Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | string
|
|
77
|
-
interface FileCollection { length; item(i); [i]; [Symbol.iterator]() } // 브라우저 FileList 호환
|
|
81
|
+
interface FileCollection { readonly length; item(i); [i]; [Symbol.iterator]() } // 브라우저 FileList 와 구조적 호환
|
|
78
82
|
```
|
|
79
83
|
|
|
80
84
|
- 업로드: `POST {hostUrl}/upload` (multipart). 헤더 `x-sd-client-name`, `Authorization: Bearer <token>`. 인증 필수.
|
|
81
|
-
- 다운로드: `GET {hostUrl}/{relPath}`
|
|
85
|
+
- 다운로드: `GET {hostUrl}/{relPath}` 응답 본문을 `Uint8Array` 로 반환. `relPath` 가 `/` 로 시작하지 않으면 자동 prepend.
|
|
82
86
|
- 브라우저 전용 `File`/`FileList` 대신 `BlobInput`/`FileCollection` 으로 Node 환경도 지원.
|
|
83
87
|
|
|
84
88
|
## ORM 클라이언트
|
|
@@ -86,30 +90,31 @@ interface FileCollection { length; item(i); [i]; [Symbol.iterator]() } // 브
|
|
|
86
90
|
```ts
|
|
87
91
|
createOrmClientConnector(client: ServiceClient): OrmClientConnector
|
|
88
92
|
interface OrmConnectOptions<T extends DbContext> {
|
|
89
|
-
DbClass: new (executor, { database
|
|
93
|
+
DbClass: new (executor: DbContextExecutor, opt: { database: string; schema?: string }) => T;
|
|
90
94
|
connOpt: DbConnOptions & { configName: string };
|
|
91
|
-
dbContextOpt?: { database: string; schema: string };
|
|
95
|
+
dbContextOpt?: { database: string; schema: string }; // 지정 시 둘 다 필수
|
|
92
96
|
}
|
|
93
97
|
connector.connect(config, async db => ...) // 트랜잭션 래핑
|
|
94
98
|
connector.connectWithoutTransaction(config, async db => ...)
|
|
95
99
|
```
|
|
96
100
|
|
|
97
|
-
-
|
|
98
|
-
- `dbContextOpt` 생략 시 서버가 알려준 `database
|
|
99
|
-
- 외래키 참조 위반
|
|
101
|
+
- 서버 `"Orm"` 서비스에 RPC 를 걸어 `getInfo` → `connect` → `executeDefs` / `executeParametrized` / `bulkInsert` / `begin·commit·rollbackTransaction` / `close` 를 위임.
|
|
102
|
+
- `dbContextOpt` 생략 시 서버가 알려준 `database` / `schema` 사용. 최종 `database` 가 빈 값이면 throw.
|
|
103
|
+
- 외래키 참조 위반 메시지(`a parent row: a foreign key constraint` / `conflicted with the REFERENCE`)는 "경고! 연관된 작업으로 인해 작업이 거부되었습니다. 후속 작업을 확인해 주세요." 로 변환되어 throw (원본은 `cause`).
|
|
104
|
+
- `OrmClientDbContextExecutor` 는 `DbContextExecutor` 구현체. 보통 connector 가 내부 생성하며 직접 인스턴스화는 커스텀 `DbContext` 구성 시에만.
|
|
100
105
|
|
|
101
|
-
##
|
|
106
|
+
## 인스턴스 이벤트 (상태/진행률)
|
|
102
107
|
|
|
103
108
|
```ts
|
|
104
109
|
client.on("state", (s: "connected" | "closed" | "reconnecting") => ...)
|
|
105
|
-
client.on("request-progress", (s: ServiceProgressState) => ...) //
|
|
106
|
-
client.on("response-progress", (s: ServiceProgressState) => ...) // 서버 →
|
|
110
|
+
client.on("request-progress", (s: ServiceProgressState) => ...) // 클라 → 서버 전송 청크 진행
|
|
111
|
+
client.on("response-progress", (s: ServiceProgressState) => ...) // 서버 → 클라 응답 청크 진행
|
|
107
112
|
client.on("server-progress", (s: ServiceProgressState) => ...) // 서버가 명시적으로 보내는 진행률
|
|
108
113
|
```
|
|
109
114
|
|
|
110
|
-
- `request-progress` 는 인코드 결과 chunk 가 2개 이상일 때만
|
|
111
|
-
- `response-progress` 는 분할 수신 중에는 서버 progress
|
|
112
|
-
- `send`
|
|
115
|
+
- `request-progress` 는 인코드 결과 chunk 가 2개 이상일 때만 0% 초기값 발행. 단일 청크면 발행 없음.
|
|
116
|
+
- `response-progress` 는 분할 수신 중에는 서버 progress 메시지, 응답 완료 시 100% 보정값으로 발행.
|
|
117
|
+
- `send` 호출에 전달한 `progress` 콜백은 인스턴스 이벤트와 함께 호출됨.
|
|
113
118
|
|
|
114
119
|
## 환경 호환 유틸
|
|
115
120
|
|
|
@@ -119,6 +124,8 @@ import { BlobInput, FileCollection, BrowserWorker,
|
|
|
119
124
|
from "@simplysm/service-client";
|
|
120
125
|
```
|
|
121
126
|
|
|
122
|
-
- `BrowserWorker` — DOM `Worker` 의
|
|
123
|
-
- `isBrowserWorkerSupported()`
|
|
124
|
-
-
|
|
127
|
+
- `BrowserWorker` — DOM `Worker` 의 최소 구조 호환 인터페이스. Node 환경 typecheck 통과용.
|
|
128
|
+
- `isBrowserWorkerSupported()` — `"Worker" in globalThis`.
|
|
129
|
+
- `isNodeWorkerSupported()` — `process.versions.node` 존재 여부.
|
|
130
|
+
- `isWorkerSupported()` — 위 둘 중 하나.
|
|
131
|
+
- 보통 직접 호출할 필요 없음 (`ServiceClient` 내부에서 분기). 환경 분기 로직을 사용자 코드에서 직접 짤 때만 사용.
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
# @simplysm/service-server
|
|
2
2
|
|
|
3
|
-
Fastify 기반 서비스 서버.
|
|
3
|
+
Fastify + WebSocket 기반 서비스 서버. 클라이언트(`@simplysm/service-client`)와 짝으로 RPC·이벤트 푸시·파일 업로드·정적 서빙·JWT 인증을 단일 포트로 제공한다.
|
|
4
4
|
|
|
5
5
|
## 사용 트리거 인덱스
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { getConfig } from "@simplysm/service-server";
|
|
17
|
-
const conf = await getConfig<{ orm: { default: DbConnConfig } }>(filePath);
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
`<rootPath>/.config.json` 같은 JSON 설정 파일을 읽고 LazyGcMap 캐시 + `FsWatcher` 로 핫리로드한다. 보통은 `ctx.getConfig(section)` 으로 충분하며, 이 함수를 직접 호출할 일은 root/client 경로 외 설정 파일을 읽을 때만 있다.
|
|
7
|
+
| 트리거 | 자료 |
|
|
8
|
+
| --- | --- |
|
|
9
|
+
| 서버 인스턴스 생성·기동·종료, 이벤트 emit, JWT 토큰 발급/검증, `ServiceServerOptions`(`rootPath`/`port`/`ssl`/`auth`/`services`/`legacyV1Handlers`) | [server.md](./server.md) |
|
|
10
|
+
| 사용자 서비스 정의(`defineService`), `ServiceContext`, `ServiceMethods` 타입 추출, `createServiceContext`, `getServiceAuthPermissions` | [define-service.md](./define-service.md) |
|
|
11
|
+
| 인증 래퍼 `auth()` (서비스/메서드 레벨, 역할 제한), `AuthTokenPayload`, `signJwt`/`verifyJwt`/`decodeJwt` | [auth.md](./auth.md) |
|
|
12
|
+
| 빌트인 서비스: `OrmService`, `AutoUpdateService`, `AppStructureService`, V1 레거시 자동 업데이트 핸들러 | [builtin-services.md](./builtin-services.md) |
|
|
13
|
+
| 내부 전송 계층(`/api/:service/:method`·`/upload`·정적 서빙, WebSocket 핸들러, 프로토콜 래퍼/워커, `ServiceSocket`, `getConfig`) — 직접 사용 시 | [internals.md](./internals.md) |
|
|
@@ -1,29 +1,37 @@
|
|
|
1
|
-
# @simplysm/service-server —
|
|
1
|
+
# @simplysm/service-server — 인증
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## `auth(fn)` / `auth(permissions, fn)`
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
서비스 factory 또는 개별 메서드를 래핑해 인증 요구사항을 부착. 래핑 함수는 호출 동작을 그대로 유지하며 내부 심볼에 `permissions: string[]` 저장.
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
}
|
|
12
|
-
```
|
|
7
|
+
- 서비스 수준: `defineService("X", auth((ctx) => ({ ... })))` — 모든 메서드 로그인 필요.
|
|
8
|
+
- 서비스 수준 + 역할: `defineService("X", auth(["admin"], (ctx) => ({ ... })))`.
|
|
9
|
+
- 메서드 수준: factory 내부에서 `someMethod: auth(() => result)`.
|
|
10
|
+
- 메서드 수준 + 역할: `someMethod: auth(["admin", "owner"], () => result)` — 배열 중 하나라도 매칭하면 통과(OR).
|
|
13
11
|
|
|
14
|
-
|
|
12
|
+
실행 시(`executeServiceMethod`) 동작:
|
|
13
|
+
|
|
14
|
+
- 메서드 권한이 있으면 서비스 권한을 **덮어쓴다**. 메서드 권한 미부착 시 서비스 권한 사용.
|
|
15
|
+
- 권한 요구 + `options.auth == null` → "auth 설정이 필요합니다" throw(서버 설정 오류).
|
|
16
|
+
- 권한 요구 + `options.auth === false` → 인증 스킵.
|
|
17
|
+
- 권한 요구 + `options.auth = { jwtSecret }` → payload 없으면 "로그인이 필요합니다" throw. `permissions.length > 0` 이면 `payload.roles` 중 하나라도 일치해야 통과, 아니면 "권한이 부족합니다" throw. `permissions.length === 0` (= `auth(fn)`) 이면 토큰만 있으면 통과.
|
|
18
|
+
|
|
19
|
+
## `AuthTokenPayload<TAuthInfo>`
|
|
15
20
|
|
|
16
|
-
|
|
21
|
+
`jose.JWTPayload` 확장.
|
|
17
22
|
|
|
18
|
-
|
|
23
|
+
- `roles: string[]` — `auth(["role"], fn)` 검사 대상.
|
|
24
|
+
- `data: TAuthInfo` — 임의 사용자 데이터. `ctx.authInfo` 로 노출.
|
|
19
25
|
|
|
20
|
-
|
|
26
|
+
## JWT 헬퍼
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
`server.signAuthToken`/`verifyAuthToken` 이 동일 알고리즘으로 감싸므로 일반 시나리오에선 그쪽을 쓴다. 직접 import 는 서버 인스턴스 없이 토큰을 다루는 경우(테스트, 별도 인증 서버, 디코드 등)만.
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
- `signJwt<T>(jwtSecret, payload): Promise<string>` — HS256, `iat` 자동, `exp = 12h` 고정. 수명 변경 필요 시 `jose.SignJWT` 직접 사용.
|
|
31
|
+
- `verifyJwt<T>(jwtSecret, token): Promise<AuthTokenPayload<T>>` — 만료: "토큰이 만료되었습니다", 그 외 위조: "유효하지 않은 토큰입니다" throw.
|
|
32
|
+
- `decodeJwt<T>(token): AuthTokenPayload<T>` — 서명 검증 없이 페이로드만 디코드. 클라이언트 측 만료 표시 등 비보안 용도에만.
|
|
25
33
|
|
|
26
|
-
##
|
|
34
|
+
## 예
|
|
27
35
|
|
|
28
36
|
```ts
|
|
29
37
|
const token = await signJwt(secret, { roles: ["admin"], data: { userId: "U1" } });
|
|
@@ -1,47 +1,71 @@
|
|
|
1
|
-
# @simplysm/service-server —
|
|
1
|
+
# @simplysm/service-server — 빌트인 서비스
|
|
2
2
|
|
|
3
|
-
`
|
|
3
|
+
`options.services` 에 추가해 사용. 각 `*ServiceType` 은 클라이언트의 `client.getService<*ServiceType>("<이름>")` 에 그대로 사용.
|
|
4
4
|
|
|
5
|
-
## `OrmService`
|
|
5
|
+
## `OrmService`
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
DB 연결을 WebSocket 세션과 묶어 원격 ORM 사용.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- 별칭: `["Orm", "SdOrmService"]`.
|
|
10
|
+
- **WebSocket 전용**. HTTP 호출 시 "WebSocket 연결이 필요합니다…" throw(트랜잭션을 소켓 라이프타임에 묶기 위함).
|
|
11
|
+
- `auth()` 래핑이라 로그인 필수. 역할 제한은 없음.
|
|
12
|
+
- 설정 소스: `ctx.getConfig<Record<string, DbConnConfig>>("orm")` → `opt.configName` 키 조회. 호출 시 `opt.config` 가 base 설정을 부분 override.
|
|
13
|
+
- 연결 ID 는 소켓별 카운터(1부터, 가장 큰 값+1). 소켓 close 시 해당 소켓의 모든 열린 DB 연결 자동 정리(에러는 warn 후 무시).
|
|
14
|
+
- `dialect === "mssql-azure"` 는 외부 노출 시 `"mssql"` 로 정규화.
|
|
10
15
|
|
|
11
|
-
메서드:
|
|
16
|
+
메서드:
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
- `getInfo(opt)` → `{ dialect, database?, schema? }` (연결 안 만들고 설정 조회만).
|
|
19
|
+
- `connect(opt): Promise<number>` — connId 반환.
|
|
20
|
+
- `close(connId)`.
|
|
21
|
+
- `beginTransaction(connId, isolationLevel?)`, `commitTransaction(connId)`, `rollbackTransaction(connId)`.
|
|
22
|
+
- `executeParametrized(connId, query, params?)` — `unknown[][]` (멀티 결과셋).
|
|
23
|
+
- `executeDefs(connId, defs: QueryDef[], options?: (ResultMeta | undefined)[])` — `options` 가 전부 null/undefined 면 단일 multi-SQL 실행 후 빈 배열들, 아니면 def 별로 build → execute → `pickResultSets` → opt 있으면 `parseQueryResult`, 없으면 raw.
|
|
24
|
+
- `bulkInsert(connId, tableName, columnDefs, records)`.
|
|
14
25
|
|
|
15
|
-
|
|
26
|
+
`opt: DbConnOptions & { configName: string }`. 타입 export: `OrmServiceType`.
|
|
16
27
|
|
|
17
|
-
|
|
28
|
+
## `AutoUpdateService`
|
|
18
29
|
|
|
19
|
-
|
|
30
|
+
- 별칭: `["AutoUpdate", "SdAutoUpdateService"]`. 인증 없음.
|
|
31
|
+
- `getLastVersion(platform: string): Promise<{ version, downloadPath } | undefined>` — `<clientPath>/<platform>/updates/` 에서 `platform === "android"` → `.apk`, 그 외 → `.exe` 중 파일명이 `^[0-9.]*$` 인 것만 후보. `semver.maxSatisfying(versions, "*")` 로 최신 선정. clientName 미설정 시 throw, 디렉터리/매칭 파일 없으면 undefined.
|
|
32
|
+
- `downloadPath` = `/<clientName>/<platform>/updates/<fileName>` (POSIX 정규화).
|
|
33
|
+
- 타입 export: `AutoUpdateServiceType`.
|
|
20
34
|
|
|
21
|
-
|
|
35
|
+
## `AppStructureService(itemsMap)`
|
|
22
36
|
|
|
23
|
-
|
|
24
|
-
import { AppStructureService } from "@simplysm/service-server";
|
|
25
|
-
import type { AppStructureItem } from "@simplysm/service-common";
|
|
26
|
-
|
|
27
|
-
const itemsMap: Record<string, AppStructureItem[]> = { admin: [...], shop: [...] };
|
|
28
|
-
services: [AppStructureService(itemsMap)];
|
|
29
|
-
```
|
|
37
|
+
다른 두 서비스와 달리 **팩토리 함수**(서비스 정의가 아님). 호출 결과를 `services` 에 등록.
|
|
30
38
|
|
|
31
|
-
`
|
|
39
|
+
- 시그니처: `AppStructureService(itemsMap: Record<string, AppStructureItem[]>): ServiceDefinition`
|
|
40
|
+
- 서비스 이름: `"AppStructure"`. 인증 없음.
|
|
41
|
+
- 메서드: `getItems(): Record<string, AppStructureItem[]>` — 주입된 map 그대로 반환.
|
|
42
|
+
- 타입 export: `AppStructureServiceType`.
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
`AppStructureItem` 은 `@simplysm/service-common` 정의(메뉴/라우트 트리 항목).
|
|
34
45
|
|
|
35
46
|
```ts
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
createServiceServer({
|
|
39
|
-
rootPath, port: 50080, auth: { jwtSecret },
|
|
40
|
-
services: [
|
|
41
|
-
OrmService,
|
|
42
|
-
AutoUpdateService,
|
|
43
|
-
AppStructureService(appItemsMap),
|
|
44
|
-
MyAppService,
|
|
45
|
-
],
|
|
46
|
-
});
|
|
47
|
+
services: [AppStructureService({ admin: [...], user: [...] })];
|
|
47
48
|
```
|
|
49
|
+
|
|
50
|
+
## V1 레거시 자동 업데이트
|
|
51
|
+
|
|
52
|
+
ver≠"2" 로 접속한 구버전 클라이언트 호환. JSON 라인 프로토콜.
|
|
53
|
+
|
|
54
|
+
### `handleV1Connection(socket, autoUpdateMethods, clientNameSetter?)` / `handleV1Connection(socket, options)`
|
|
55
|
+
|
|
56
|
+
`ServiceServer` 가 자동 호출. `services` 에 `AutoUpdate` 가 있으면 그 factory 의 `getLastVersion` 을 V1 메서드로 자동 매핑. `legacyV1Handlers` 도 그대로 전달. 둘 다 없으면 1008 거부.
|
|
57
|
+
|
|
58
|
+
`V1ConnectionOptions`:
|
|
59
|
+
|
|
60
|
+
- `serviceContext?` 또는 `serviceContextFactory?: (req: V1Request) => ServiceContext` — 핸들러용 컨텍스트(둘 중 하나).
|
|
61
|
+
- `handlers?: V1RequestHandler[]` — 우선 실행. 각 핸들러는 `{ handled: true, state?: "success"|"error", body } | { handled: false }` 반환. handled=true 면 즉시 응답.
|
|
62
|
+
- `autoUpdateMethods?` 또는 `autoUpdateMethodsFactory?: (ctx) => V1AutoUpdateMethods` — 모든 사용자 핸들러가 미처리 + `command === "SdAutoUpdateService.getLastVersion"` 일 때 fallback.
|
|
63
|
+
- `clientNameSetter?: (clientName: string | undefined) => void` — 매 요청의 `clientName` 알림 콜백.
|
|
64
|
+
- 어디서도 처리 안 되면 `{ code: "UPGRADE_REQUIRED", message: "앱 업그레이드가 필요합니다." }` 응답.
|
|
65
|
+
|
|
66
|
+
타입:
|
|
67
|
+
|
|
68
|
+
- `V1Request = { uuid, command, params: unknown[], clientName? }`
|
|
69
|
+
- `V1Response = { name: "response", reqUuid, state: "success" | "error", body }`
|
|
70
|
+
- `V1AutoUpdateMethods = { getLastVersion(platform): Promise<unknown> | unknown }`
|
|
71
|
+
- `V1RequestHandler`, `V1RequestHandlerContext`, `V1RequestHandlerResult`.
|
|
@@ -1,70 +1,54 @@
|
|
|
1
|
-
# @simplysm/service-server —
|
|
1
|
+
# @simplysm/service-server — 서비스 정의
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
## `defineService(name, factory): ServiceDefinition<TMethods>`
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
- `name: string | string[]` — 단일 또는 별칭 목록. 첫 요소가 primary(`def.name`), 전체가 `def.names`. RPC 라우팅은 `names.includes(요청서비스명)` 매칭. 빈 배열이면 throw.
|
|
6
|
+
- `factory: (ctx: ServiceContext) => TMethods` — 매 요청마다 호출되어 메서드 객체 생성(컨텍스트 캡처). factory 자체가 `auth(...)` 래핑이면 `authPermissions` 가 자동 추출돼 서비스 수준 인증으로 승격.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
defineService<TMethods extends Record<string, (...args: any[]) => any>>(
|
|
9
|
-
name: string | string[], // 다중 이름 = alias (예: ["Orm", "SdOrmService"])
|
|
10
|
-
factory: (ctx: ServiceContext) => TMethods,
|
|
11
|
-
): ServiceDefinition<TMethods>;
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
`factory` 는 호출마다 실행돼 메서드 객체를 생성한다 (요청별 컨텍스트 캡처). `factory` 가 `auth(...)` 로 감싸져 있으면 서비스 수준 인증으로 승격된다.
|
|
8
|
+
반환 `ServiceDefinition<TMethods>`:
|
|
15
9
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
auth(["admin", "owner"], fn) // 해당 역할 중 하나 필요 (OR)
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
- 서비스 수준: `defineService("X", auth((ctx) => ({ ... })))`
|
|
24
|
-
- 메서드 수준: 팩토리 안에서 메서드를 `auth(["admin"], () => result)` 로 감싼다. 메서드 권한이 있으면 서비스 권한을 **덮어쓴다**.
|
|
25
|
-
- `auth: false` 옵션 시 검증 스킵, `auth: undefined` 인데 auth 필요 서비스 등록 시 `listen()` throw, auth 설정됐는데 토큰 없거나 권한 부족 시 메서드 호출에서 throw (`로그인이 필요합니다.` / `권한이 부족합니다.`).
|
|
10
|
+
- `name: string`
|
|
11
|
+
- `names: string[]`
|
|
12
|
+
- `factory`
|
|
13
|
+
- `authPermissions?: string[]`
|
|
26
14
|
|
|
27
15
|
## `ServiceContext<TAuthInfo>`
|
|
28
16
|
|
|
29
|
-
서비스
|
|
17
|
+
서비스 factory 가 받는 요청별 컨텍스트.
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
19
|
+
- `server: ServiceServer<TAuthInfo>`
|
|
20
|
+
- `socket?: ServiceSocket` — WebSocket 경로일 때만.
|
|
21
|
+
- `http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }` — HTTP 경로.
|
|
22
|
+
- `legacy?: { clientName?: string }` — V1 경로.
|
|
23
|
+
- `get authInfo(): TAuthInfo | undefined` — socket 의 payload.data 우선, 없으면 http.
|
|
24
|
+
- `get clientName(): string | undefined` — socket → http → legacy 순. 빈 문자/`..`/`/`/`\\` 포함 시 throw.
|
|
25
|
+
- `get clientPath(): string | undefined` — `<rootPath>/www/<clientName>`. clientName 없으면 undefined.
|
|
26
|
+
- `getConfig<T>(section: string): Promise<T>` — `<rootPath>/.config.json` + `<clientPath>/.config.json` 을 `obj.merge` 한 뒤 `section` 키 반환. 섹션 없으면 throw. 설정 파일은 `FsWatcher` 로 변경 감시되어 자동 리로드.
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
get clientName(): string | undefined; // socket → http → legacy 순. 위험문자(.. / \) throw
|
|
40
|
-
get clientPath(): string | undefined; // <rootPath>/www/<clientName>
|
|
41
|
-
getConfig<T>(section: string): Promise<T>; // root + clientPath 의 .config.json merge, 누락 시 throw
|
|
42
|
-
}
|
|
43
|
-
```
|
|
28
|
+
## `ServiceMethods<TDefinition>` 타입
|
|
44
29
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
클라이언트에서 메서드 시그니처만 공유하기 위한 추출 타입.
|
|
30
|
+
`ServiceDefinition<M>` 에서 `M` 추출. 클라이언트의 `client.getService<MyServiceType>("MyService")` 에 사용.
|
|
48
31
|
|
|
49
32
|
```ts
|
|
50
|
-
export const UserService = defineService("User", (ctx) => ({
|
|
33
|
+
export const UserService = defineService("User", (ctx) => ({
|
|
34
|
+
getProfile: () => ({ name: "kim" }),
|
|
35
|
+
}));
|
|
51
36
|
export type UserServiceType = ServiceMethods<typeof UserService>;
|
|
52
|
-
// 클라이언트: client.getService<UserServiceType>("User")
|
|
53
37
|
```
|
|
54
38
|
|
|
55
|
-
## 보조
|
|
39
|
+
## 보조 export
|
|
56
40
|
|
|
57
|
-
- `createServiceContext(server, socket?, http?, legacy?)` — 컨텍스트
|
|
58
|
-
- `getServiceAuthPermissions(fn)` — `auth()` 가 함수에
|
|
41
|
+
- `createServiceContext(server, socket?, http?, legacy?): ServiceContext` — 컨텍스트 직조. 커스텀 라우트/테스트용.
|
|
42
|
+
- `getServiceAuthPermissions(fn): string[] | undefined` — `auth()` 가 함수에 심어둔 권한 배열 추출. 래핑 안 됐으면 undefined. 내부 executor 가 사용.
|
|
59
43
|
|
|
60
|
-
##
|
|
44
|
+
## 예
|
|
61
45
|
|
|
62
46
|
```ts
|
|
63
47
|
const HealthService = defineService("Health", () => ({
|
|
64
48
|
check: () => ({ status: "ok" }),
|
|
65
49
|
}));
|
|
66
50
|
|
|
67
|
-
const UserService = defineService("User", auth((ctx) => ({
|
|
51
|
+
const UserService = defineService(["User", "MyUser"], auth((ctx) => ({
|
|
68
52
|
me: () => ctx.authInfo,
|
|
69
53
|
adminOnly: auth(["admin"], () => "ok"),
|
|
70
54
|
})));
|