@simplysm/service-server 14.0.48 → 14.0.51
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/README.md +53 -82
- package/dist/auth/jwt-manager.js.map +1 -1
- package/dist/services/orm-service.js.map +1 -1
- package/dist/transport/socket/websocket-handler.js.map +1 -1
- package/docs/auth/auth-token-payload.md +18 -0
- package/docs/auth/sign-jwt.md +30 -0
- package/docs/auth/verify-jwt.md +35 -0
- package/docs/core/auth.md +58 -0
- package/docs/core/define-service.md +76 -0
- package/docs/core/execute-service-method.md +38 -0
- package/docs/core/service-context.md +79 -0
- package/docs/{legacy.md → legacy/handle-v1-connection.md} +5 -5
- package/docs/main/create-service-server.md +32 -0
- package/docs/main/service-server.md +106 -0
- package/docs/{protocol.md → protocol/server-protocol-wrapper.md} +11 -9
- package/docs/services/app-structure-service.md +54 -0
- package/docs/services/auto-update-service.md +29 -0
- package/docs/services/orm-service.md +38 -0
- package/docs/transport-http/handle-http-request.md +33 -0
- package/docs/transport-http/handle-static-file.md +29 -0
- package/docs/transport-http/handle-upload.md +33 -0
- package/docs/transport-socket/service-socket.md +64 -0
- package/docs/transport-socket/websocket-handler.md +57 -0
- package/docs/{types.md → types/service-server-options.md} +4 -4
- package/docs/{utils.md → utils/get-config.md} +8 -6
- package/package.json +7 -7
- package/src/auth/jwt-manager.ts +1 -1
- package/src/services/orm-service.ts +1 -1
- package/src/transport/socket/websocket-handler.ts +4 -4
- package/docs/auth.md +0 -64
- package/docs/core.md +0 -174
- package/docs/main.md +0 -88
- package/docs/services.md +0 -94
- package/docs/transport-http.md +0 -93
- package/docs/transport-socket.md +0 -119
package/docs/auth.md
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
# Auth
|
|
2
|
-
|
|
3
|
-
## `AuthTokenPayload`
|
|
4
|
-
|
|
5
|
-
JWT 페이로드 인터페이스. `jose` 라이브러리의 `JWTPayload`를 확장한다.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
interface AuthTokenPayload<TAuthInfo = unknown> extends JWTPayload {
|
|
9
|
-
roles: string[];
|
|
10
|
-
data: TAuthInfo;
|
|
11
|
-
}
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
| Field | Type | Description |
|
|
15
|
-
|-------|------|-------------|
|
|
16
|
-
| `roles` | `string[]` | 사용자 역할 배열. `auth(["admin"], ...)` 등에서 역할 검사에 사용된다 |
|
|
17
|
-
| `data` | `TAuthInfo` | 사용자 정의 인증 데이터. `ServiceContext.authInfo`로 접근 가능하다 |
|
|
18
|
-
| (JWTPayload 상속) | — | `iss`, `sub`, `aud`, `exp`, `nbf`, `iat`, `jti` 등 표준 JWT 클레임 |
|
|
19
|
-
|
|
20
|
-
## `signJwt`
|
|
21
|
-
|
|
22
|
-
HS256 알고리즘과 12시간 유효기간으로 JWT 토큰을 서명한다.
|
|
23
|
-
|
|
24
|
-
```typescript
|
|
25
|
-
async function signJwt<TAuthInfo = unknown>(
|
|
26
|
-
jwtSecret: string,
|
|
27
|
-
payload: AuthTokenPayload<TAuthInfo>,
|
|
28
|
-
): Promise<string>;
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
| Parameter | Type | Description |
|
|
32
|
-
|-----------|------|-------------|
|
|
33
|
-
| `jwtSecret` | `string` | HMAC 서명 시크릿 |
|
|
34
|
-
| `payload` | `AuthTokenPayload<TAuthInfo>` | JWT 페이로드 |
|
|
35
|
-
|
|
36
|
-
반환값: 서명된 JWT 토큰 문자열.
|
|
37
|
-
|
|
38
|
-
## `verifyJwt`
|
|
39
|
-
|
|
40
|
-
JWT 토큰을 검증하고 페이로드를 반환한다. 만료된 토큰은 "토큰이 만료되었습니다." 에러를, 그 외 유효하지 않은 토큰은 "유효하지 않은 토큰입니다." 에러를 던진다.
|
|
41
|
-
|
|
42
|
-
```typescript
|
|
43
|
-
async function verifyJwt<TAuthInfo = unknown>(
|
|
44
|
-
jwtSecret: string,
|
|
45
|
-
token: string,
|
|
46
|
-
): Promise<AuthTokenPayload<TAuthInfo>>;
|
|
47
|
-
```
|
|
48
|
-
|
|
49
|
-
| Parameter | Type | Description |
|
|
50
|
-
|-----------|------|-------------|
|
|
51
|
-
| `jwtSecret` | `string` | HMAC 서명 시크릿 |
|
|
52
|
-
| `token` | `string` | 검증할 JWT 토큰 문자열 |
|
|
53
|
-
|
|
54
|
-
## `decodeJwt`
|
|
55
|
-
|
|
56
|
-
JWT 토큰을 검증 없이 디코딩한다. 서명 검증이나 만료 확인을 수행하지 않는다.
|
|
57
|
-
|
|
58
|
-
```typescript
|
|
59
|
-
function decodeJwt<TAuthInfo = unknown>(token: string): AuthTokenPayload<TAuthInfo>;
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
| Parameter | Type | Description |
|
|
63
|
-
|-----------|------|-------------|
|
|
64
|
-
| `token` | `string` | 디코딩할 JWT 토큰 문자열 |
|
package/docs/core.md
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
# Core
|
|
2
|
-
|
|
3
|
-
## `ServiceContext`
|
|
4
|
-
|
|
5
|
-
서비스 팩토리 함수에 전달되는 컨텍스트 인터페이스. 전송 방식(WebSocket/HTTP)에 무관하게 동일한 인터페이스를 제공한다.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
interface ServiceContext<TAuthInfo = unknown> {
|
|
9
|
-
server: ServiceServer<TAuthInfo>;
|
|
10
|
-
socket?: ServiceSocket;
|
|
11
|
-
http?: {
|
|
12
|
-
clientName: string;
|
|
13
|
-
authTokenPayload?: AuthTokenPayload<TAuthInfo>;
|
|
14
|
-
};
|
|
15
|
-
legacy?: {
|
|
16
|
-
clientName?: string;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
get authInfo(): TAuthInfo | undefined;
|
|
20
|
-
get clientName(): string | undefined;
|
|
21
|
-
get clientPath(): string | undefined;
|
|
22
|
-
getConfig<T>(section: string): Promise<T>;
|
|
23
|
-
}
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
| Field | Type | Description |
|
|
27
|
-
|-------|------|-------------|
|
|
28
|
-
| `server` | `ServiceServer<TAuthInfo>` | 서버 인스턴스 참조 |
|
|
29
|
-
| `socket` | `ServiceSocket` (optional) | WebSocket 요청일 때만 존재하는 소켓 객체 |
|
|
30
|
-
| `http` | `{ clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> }` (optional) | HTTP 요청일 때만 존재 |
|
|
31
|
-
| `legacy` | `{ clientName?: string }` (optional) | V1 레거시 컨텍스트 (자동 업데이트 전용) |
|
|
32
|
-
|
|
33
|
-
| Accessor/Method | Return Type | Description |
|
|
34
|
-
|------------------|-------------|-------------|
|
|
35
|
-
| `authInfo` | `TAuthInfo \| undefined` | 토큰에서 추출한 사용자 데이터. WebSocket은 소켓의 `authTokenPayload.data`, HTTP는 헤더의 `authTokenPayload.data`에서 읽는다 |
|
|
36
|
-
| `clientName` | `string \| undefined` | 클라이언트 앱 이름. `..`, `/`, `\`를 포함하면 에러를 던진다 (경로 탐색 공격 방지) |
|
|
37
|
-
| `clientPath` | `string \| undefined` | `{rootPath}/www/{clientName}` 경로. `clientName`이 없으면 `undefined` |
|
|
38
|
-
| `getConfig<T>(section)` | `Promise<T>` | `.config.json`에서 섹션을 읽는다. 루트 설정을 먼저 읽고 클라이언트별 설정으로 덮어쓴다. 섹션이 없으면 에러를 던진다 |
|
|
39
|
-
|
|
40
|
-
## `createServiceContext`
|
|
41
|
-
|
|
42
|
-
`ServiceContext` 인스턴스를 생성한다.
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
function createServiceContext<TAuthInfo = unknown>(
|
|
46
|
-
server: ServiceServer<TAuthInfo>,
|
|
47
|
-
socket?: ServiceSocket,
|
|
48
|
-
http?: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> },
|
|
49
|
-
legacy?: { clientName?: string },
|
|
50
|
-
): ServiceContext<TAuthInfo>;
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
| Parameter | Type | Description |
|
|
54
|
-
|-----------|------|-------------|
|
|
55
|
-
| `server` | `ServiceServer<TAuthInfo>` | 서버 인스턴스 |
|
|
56
|
-
| `socket` | `ServiceSocket` (optional) | WebSocket 연결 (WebSocket 요청 시) |
|
|
57
|
-
| `http` | `{ clientName: string; authTokenPayload? }` (optional) | HTTP 요청 정보 |
|
|
58
|
-
| `legacy` | `{ clientName?: string }` (optional) | V1 레거시 컨텍스트 |
|
|
59
|
-
|
|
60
|
-
## `auth`
|
|
61
|
-
|
|
62
|
-
서비스 팩토리 또는 메서드에 인증을 요구하는 래퍼 함수. 래핑된 함수에 `AUTH_PERMISSIONS` 심볼로 권한 배열을 부착한다.
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
function auth<TFunction extends (...args: any[]) => any>(fn: TFunction): TFunction;
|
|
66
|
-
function auth<TFunction extends (...args: any[]) => any>(
|
|
67
|
-
permissions: string[],
|
|
68
|
-
fn: TFunction,
|
|
69
|
-
): TFunction;
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
| Overload | Description |
|
|
73
|
-
|----------|-------------|
|
|
74
|
-
| `auth(fn)` | 로그인만 요구 (역할 검사 없음). 권한 배열은 `[]` |
|
|
75
|
-
| `auth(permissions, fn)` | 지정된 역할 중 하나를 가진 사용자만 허용 |
|
|
76
|
-
|
|
77
|
-
사용 위치:
|
|
78
|
-
- 서비스 수준: `defineService("Name", auth((ctx) => ({ ... })))` — 모든 메서드에 인증 적용
|
|
79
|
-
- 메서드 수준: `{ methodName: auth(() => result) }` — 해당 메서드만 인증 적용
|
|
80
|
-
- 메서드 수준이 서비스 수준보다 우선한다
|
|
81
|
-
|
|
82
|
-
## `getServiceAuthPermissions`
|
|
83
|
-
|
|
84
|
-
`auth()`로 래핑된 함수에서 인증 권한 배열을 읽는다. 래핑되지 않은 함수는 `undefined`를 반환한다.
|
|
85
|
-
|
|
86
|
-
```typescript
|
|
87
|
-
function getServiceAuthPermissions(fn: Function): string[] | undefined;
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
| Parameter | Type | Description |
|
|
91
|
-
|-----------|------|-------------|
|
|
92
|
-
| `fn` | `Function` | 검사할 함수 |
|
|
93
|
-
|
|
94
|
-
## `ServiceDefinition`
|
|
95
|
-
|
|
96
|
-
서비스 정의 구조체.
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
interface ServiceDefinition<TMethods = Record<string, (...args: any[]) => any>> {
|
|
100
|
-
name: string;
|
|
101
|
-
factory: (ctx: ServiceContext) => TMethods;
|
|
102
|
-
authPermissions?: string[];
|
|
103
|
-
}
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
| Field | Type | Description |
|
|
107
|
-
|-------|------|-------------|
|
|
108
|
-
| `name` | `string` | 서비스 이름. HTTP에서 `/api/{name}/{method}`, WebSocket에서 `{name}.{method}`로 라우팅된다 |
|
|
109
|
-
| `factory` | `(ctx: ServiceContext) => TMethods` | 요청마다 호출되는 팩토리 함수. 메서드 객체를 반환한다 |
|
|
110
|
-
| `authPermissions` | `string[]` (optional) | `auth()`로 래핑된 팩토리의 경우 서비스 수준 권한 배열 |
|
|
111
|
-
|
|
112
|
-
## `defineService`
|
|
113
|
-
|
|
114
|
-
이름과 팩토리 함수로 서비스를 정의한다. 팩토리가 `auth()`로 래핑되어 있으면 자동으로 `authPermissions`를 추출한다.
|
|
115
|
-
|
|
116
|
-
```typescript
|
|
117
|
-
function defineService<TMethods extends Record<string, (...args: any[]) => any>>(
|
|
118
|
-
name: string,
|
|
119
|
-
factory: (ctx: ServiceContext) => TMethods,
|
|
120
|
-
): ServiceDefinition<TMethods>;
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
| Parameter | Type | Description |
|
|
124
|
-
|-----------|------|-------------|
|
|
125
|
-
| `name` | `string` | 서비스 이름 |
|
|
126
|
-
| `factory` | `(ctx: ServiceContext) => TMethods` | 메서드 객체를 반환하는 팩토리 함수 |
|
|
127
|
-
|
|
128
|
-
## `ServiceMethods`
|
|
129
|
-
|
|
130
|
-
`ServiceDefinition`에서 메서드 시그니처를 추출하는 유틸리티 타입. 클라이언트 측 타입 공유에 사용한다.
|
|
131
|
-
|
|
132
|
-
```typescript
|
|
133
|
-
type ServiceMethods<TDefinition> =
|
|
134
|
-
TDefinition extends ServiceDefinition<infer M> ? M : never;
|
|
135
|
-
```
|
|
136
|
-
|
|
137
|
-
사용 예:
|
|
138
|
-
|
|
139
|
-
```typescript
|
|
140
|
-
export type UserServiceType = ServiceMethods<typeof UserService>;
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## `executeServiceMethod`
|
|
144
|
-
|
|
145
|
-
서비스 조회 -> 컨텍스트 생성 -> 인증 확인 -> 메서드 실행 파이프라인을 수행한다.
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
async function executeServiceMethod(
|
|
149
|
-
server: ServiceServer,
|
|
150
|
-
def: {
|
|
151
|
-
serviceName: string;
|
|
152
|
-
methodName: string;
|
|
153
|
-
params: unknown[];
|
|
154
|
-
socket?: ServiceSocket;
|
|
155
|
-
http?: { clientName: string; authTokenPayload?: AuthTokenPayload };
|
|
156
|
-
},
|
|
157
|
-
): Promise<unknown>;
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
| Parameter | Type | Description |
|
|
161
|
-
|-----------|------|-------------|
|
|
162
|
-
| `server` | `ServiceServer` | 서버 인스턴스 |
|
|
163
|
-
| `def.serviceName` | `string` | 호출할 서비스 이름 |
|
|
164
|
-
| `def.methodName` | `string` | 호출할 메서드 이름 |
|
|
165
|
-
| `def.params` | `unknown[]` | 메서드 매개변수 배열 |
|
|
166
|
-
| `def.socket` | `ServiceSocket` (optional) | WebSocket 연결 (WebSocket 요청 시) |
|
|
167
|
-
| `def.http` | `{ clientName: string; authTokenPayload? }` (optional) | HTTP 요청 정보 |
|
|
168
|
-
|
|
169
|
-
인증 검사 로직:
|
|
170
|
-
|
|
171
|
-
1. 메서드 수준 `auth()` 권한이 있으면 이를 사용하고, 없으면 서비스 수준 `authPermissions`를 사용한다
|
|
172
|
-
2. 인증이 필요한데 `server.options.auth`가 `undefined`이면 설정 오류로 에러를 던진다
|
|
173
|
-
3. `server.options.auth`가 `false`이면 인증 검사를 스킵한다
|
|
174
|
-
4. 인증이 활성화되어 있으면 토큰 존재 여부를 확인하고, 역할 배열이 비어있지 않으면 역할 매칭을 수행한다
|
package/docs/main.md
DELETED
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Main
|
|
2
|
-
|
|
3
|
-
## `ServiceServer`
|
|
4
|
-
|
|
5
|
-
Fastify를 래핑한 서비스 서버 클래스. WebSocket/HTTP 이중 전송, JWT 인증, 이벤트 브로드캐스트, graceful shutdown을 처리한다. `EventEmitter<{ ready: void; close: void }>`를 확장한다.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
class ServiceServer<TAuthInfo = unknown> extends EventEmitter<{
|
|
9
|
-
ready: void;
|
|
10
|
-
close: void;
|
|
11
|
-
}> {
|
|
12
|
-
isOpen: boolean;
|
|
13
|
-
readonly fastify: FastifyInstance;
|
|
14
|
-
readonly options: ServiceServerOptions;
|
|
15
|
-
|
|
16
|
-
constructor(options: ServiceServerOptions);
|
|
17
|
-
|
|
18
|
-
async listen(): Promise<void>;
|
|
19
|
-
async close(): Promise<void>;
|
|
20
|
-
getEvent<TEventDef extends ServiceEventDef>(eventName: string): ServerEventProxy<TEventDef>;
|
|
21
|
-
async emitEvent<TEventDef extends ServiceEventDef>(
|
|
22
|
-
eventName: string,
|
|
23
|
-
infoSelector: (item: TEventDef["$info"]) => boolean,
|
|
24
|
-
data: TEventDef["$data"],
|
|
25
|
-
): Promise<void>;
|
|
26
|
-
async signAuthToken(payload: AuthTokenPayload<TAuthInfo>): Promise<string>;
|
|
27
|
-
async verifyAuthToken(token: string): Promise<AuthTokenPayload<TAuthInfo>>;
|
|
28
|
-
}
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
| Member | Type | Description |
|
|
32
|
-
|--------|------|-------------|
|
|
33
|
-
| `isOpen` | `boolean` | 서버가 리스닝 중인지 여부 |
|
|
34
|
-
| `fastify` | `FastifyInstance` | 내부 Fastify 인스턴스. 직접 접근이 필요할 때 사용 |
|
|
35
|
-
| `options` | `ServiceServerOptions` | 생성 시 전달된 옵션 |
|
|
36
|
-
|
|
37
|
-
| Method | Description |
|
|
38
|
-
|--------|-------------|
|
|
39
|
-
| `listen()` | 서버를 시작한다. 플러그인 등록, 라우트 설정, SIGINT/SIGTERM 핸들러 등록을 수행한다. 완료 시 `ready` 이벤트를 발생시킨다 |
|
|
40
|
-
| `close()` | 모든 WebSocket 연결을 닫고 Fastify 서버를 종료한다. 완료 시 `close` 이벤트를 발생시킨다 |
|
|
41
|
-
| `getEvent(eventName)` | 타입 안전한 이벤트 프록시(`ServerEventProxy<TEventDef>`)를 반환한다. `emit(infoSelector, data)` 메서드를 포함. `getService()` 패턴과 동일 |
|
|
42
|
-
| `emitEvent(eventName, infoSelector, data)` | `eventName`에 해당하는 이벤트 리스너 중 `infoSelector`에 매칭되는 WebSocket 클라이언트에 이벤트를 브로드캐스트한다. 제네릭 `TEventDef`로 타입 안전성 보장 |
|
|
43
|
-
| `signAuthToken(payload)` | JWT 토큰을 서명한다. `options.auth`가 설정되지 않으면 에러를 던진다 |
|
|
44
|
-
| `verifyAuthToken(token)` | JWT 토큰을 검증하고 페이로드를 반환한다. `options.auth`가 설정되지 않으면 에러를 던진다 |
|
|
45
|
-
|
|
46
|
-
`listen()` 시 등록되는 라우트:
|
|
47
|
-
|
|
48
|
-
| Route | Method | Handler |
|
|
49
|
-
|-------|--------|---------|
|
|
50
|
-
| `/api/:service/:method` | GET/POST | `handleHttpRequest` -- 서비스 메서드 호출 |
|
|
51
|
-
| `/upload` | POST | `handleUpload` -- multipart 파일 업로드 |
|
|
52
|
-
| `/`, `/ws` | WebSocket | WebSocket 핸들러. `ver=2` 쿼리 시 V2 프로토콜, 그 외 V1 레거시 |
|
|
53
|
-
| `/*` | GET/POST/PUT/DELETE/PATCH/HEAD | `handleStaticFile` -- 정적 파일 서빙 |
|
|
54
|
-
|
|
55
|
-
`listen()` 시 Fastify 플러그인 등록 순서: `@fastify/websocket` -> `@fastify/helmet` -> `@fastify/multipart` -> `@fastify/static` -> `@fastify/cors`
|
|
56
|
-
|
|
57
|
-
Graceful shutdown: `SIGINT`/`SIGTERM` 시그널 수신 시 `close()`를 호출하고, 10초 내에 종료되지 않으면 `process.exit(1)`로 강제 종료한다.
|
|
58
|
-
|
|
59
|
-
## `ServerEventProxy`
|
|
60
|
-
|
|
61
|
-
`getEvent()`가 반환하는 타입 안전한 이벤트 프록시 인터페이스. `emit` 메서드만 포함한다.
|
|
62
|
-
|
|
63
|
-
```typescript
|
|
64
|
-
interface ServerEventProxy<TEventDef extends ServiceEventDef> {
|
|
65
|
-
emit(
|
|
66
|
-
infoSelector: (item: TEventDef["$info"]) => boolean,
|
|
67
|
-
data: TEventDef["$data"],
|
|
68
|
-
): Promise<void>;
|
|
69
|
-
}
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
| Method | Description |
|
|
73
|
-
|--------|-------------|
|
|
74
|
-
| `emit(infoSelector, data)` | `infoSelector`에 매칭되는 이벤트 리스너를 가진 WebSocket 클라이언트에 이벤트를 브로드캐스트한다 |
|
|
75
|
-
|
|
76
|
-
## `createServiceServer`
|
|
77
|
-
|
|
78
|
-
`ServiceServer` 인스턴스를 생성하는 팩토리 함수.
|
|
79
|
-
|
|
80
|
-
```typescript
|
|
81
|
-
function createServiceServer<TAuthInfo = unknown>(
|
|
82
|
-
options: ServiceServerOptions,
|
|
83
|
-
): ServiceServer<TAuthInfo>;
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
| Parameter | Type | Description |
|
|
87
|
-
|-----------|------|-------------|
|
|
88
|
-
| `options` | `ServiceServerOptions` | 서버 설정 옵션 |
|
package/docs/services.md
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
# Services
|
|
2
|
-
|
|
3
|
-
## `OrmService`
|
|
4
|
-
|
|
5
|
-
ORM 브리지 서비스 정의. WebSocket 전용이며 `auth()`로 래핑되어 로그인이 필수다. `defineService("Orm", auth((ctx) => ...))`로 정의되어 있다.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
const OrmService: ServiceDefinition;
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
소켓별 DB 연결 관리:
|
|
12
|
-
- `WeakMap<ServiceSocket, Map<number, DbConn>>`으로 소켓별 연결 상태를 관리한다
|
|
13
|
-
- 소켓이 닫히면 해당 소켓의 열린 DB 연결을 모두 자동 종료한다
|
|
14
|
-
- `getConfig("orm")`에서 `configName`으로 DB 연결 설정을 읽는다
|
|
15
|
-
|
|
16
|
-
제공 메서드:
|
|
17
|
-
|
|
18
|
-
| Method | Signature | Description |
|
|
19
|
-
|--------|-----------|-------------|
|
|
20
|
-
| `getInfo` | `(opt: DbConnOptions & { configName: string }) => Promise<{ dialect: Dialect; database?: string; schema?: string }>` | DB 연결 정보를 반환한다. `mssql-azure` dialect은 `mssql`로 변환된다 |
|
|
21
|
-
| `connect` | `(opt: DbConnOptions & { configName: string }) => Promise<number>` | DB에 연결하고 연결 ID를 반환한다 |
|
|
22
|
-
| `close` | `(connId: number) => Promise<void>` | DB 연결을 종료한다 |
|
|
23
|
-
| `beginTransaction` | `(connId: number, isolationLevel?: IsolationLevel) => Promise<void>` | 트랜잭션을 시작한다 |
|
|
24
|
-
| `commitTransaction` | `(connId: number) => Promise<void>` | 트랜잭션을 커밋한다 |
|
|
25
|
-
| `rollbackTransaction` | `(connId: number) => Promise<void>` | 트랜잭션을 롤백한다 |
|
|
26
|
-
| `executeParametrized` | `(connId: number, query: string, params?: unknown[]) => Promise<unknown[][]>` | 파라미터화된 쿼리를 실행한다 |
|
|
27
|
-
| `executeDefs` | `(connId: number, defs: QueryDef[], options?: (ResultMeta \| undefined)[]) => Promise<unknown[][]>` | QueryDef 배열을 SQL로 변환하여 실행한다. `options`가 모두 `null`이면 쿼리를 합쳐 한 번에 실행한다 |
|
|
28
|
-
| `bulkInsert` | `(connId: number, tableName: string, columnDefs: Record<string, ColumnMeta>, records: Record<string, unknown>[]) => Promise<void>` | 대량 삽입을 수행한다 |
|
|
29
|
-
|
|
30
|
-
HTTP 요청 시 "WebSocket 연결이 필요합니다" 에러를 던진다.
|
|
31
|
-
|
|
32
|
-
## `OrmServiceType`
|
|
33
|
-
|
|
34
|
-
`OrmService`의 메서드 시그니처 타입. 클라이언트 측 타입 공유에 사용한다.
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
type OrmServiceType = ServiceMethods<typeof OrmService>;
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## `AutoUpdateService`
|
|
41
|
-
|
|
42
|
-
자동 업데이트 서비스 정의. `defineService("AutoUpdate", (ctx) => ...)`로 정의되어 있다. 인증 불필요.
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
const AutoUpdateService: ServiceDefinition;
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
제공 메서드:
|
|
49
|
-
|
|
50
|
-
| Method | Signature | Description |
|
|
51
|
-
|--------|-----------|-------------|
|
|
52
|
-
| `getLastVersion` | `(platform: string) => Promise<{ version: string; downloadPath: string } \| undefined>` | `{clientPath}/{platform}/updates/` 디렉토리에서 최신 버전 파일을 찾아 반환한다 |
|
|
53
|
-
|
|
54
|
-
`getLastVersion` 동작:
|
|
55
|
-
- `platform`이 `"android"`이면 `.apk` 파일을, 그 외에는 `.exe` 파일을 탐색한다
|
|
56
|
-
- 파일명이 `{version}.{ext}` 형식이어야 하며 (예: `1.2.3.apk`), `semver.maxSatisfying`으로 최대 버전을 결정한다
|
|
57
|
-
- `clientPath`가 없으면 에러를 던진다
|
|
58
|
-
- 업데이트 디렉토리나 매칭 파일이 없으면 `undefined`를 반환한다
|
|
59
|
-
|
|
60
|
-
## `AutoUpdateServiceType`
|
|
61
|
-
|
|
62
|
-
`AutoUpdateService`의 메서드 시그니처 타입.
|
|
63
|
-
|
|
64
|
-
```typescript
|
|
65
|
-
type AutoUpdateServiceType = ServiceMethods<typeof AutoUpdateService>;
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## `AppStructureService`
|
|
69
|
-
|
|
70
|
-
앱 구조 정보 서비스를 생성하는 팩토리 함수. `defineService`를 래핑하여 `Record<string, AppStructureItem[]>` 맵을 받아 서비스 정의를 반환한다. 인증 불필요.
|
|
71
|
-
|
|
72
|
-
```typescript
|
|
73
|
-
function AppStructureService(
|
|
74
|
-
itemsMap: Record<string, AppStructureItem[]>,
|
|
75
|
-
): ServiceDefinition;
|
|
76
|
-
```
|
|
77
|
-
|
|
78
|
-
| Parameter | Type | Description |
|
|
79
|
-
|-----------|------|-------------|
|
|
80
|
-
| `itemsMap` | `Record<string, AppStructureItem[]>` | 앱 구조 아이템 맵. `AppStructureItem`은 `@simplysm/service-common`에서 import한다 |
|
|
81
|
-
|
|
82
|
-
제공 메서드:
|
|
83
|
-
|
|
84
|
-
| Method | Signature | Description |
|
|
85
|
-
|--------|-----------|-------------|
|
|
86
|
-
| `getItems` | `() => Record<string, AppStructureItem[]>` | 생성 시 전달된 `itemsMap`을 그대로 반환한다 |
|
|
87
|
-
|
|
88
|
-
## `AppStructureServiceType`
|
|
89
|
-
|
|
90
|
-
`AppStructureService`가 반환하는 서비스의 메서드 시그니처 타입.
|
|
91
|
-
|
|
92
|
-
```typescript
|
|
93
|
-
type AppStructureServiceType = ServiceMethods<ReturnType<typeof AppStructureService>>;
|
|
94
|
-
```
|
package/docs/transport-http.md
DELETED
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
# Transport - HTTP
|
|
2
|
-
|
|
3
|
-
## `handleHttpRequest`
|
|
4
|
-
|
|
5
|
-
GET/POST `/api/:service/:method` 경로의 HTTP 요청을 처리한다. `x-sd-client-name` 헤더가 필수이며, `Authorization` 헤더가 있으면 JWT 토큰을 검증한다.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
async function handleHttpRequest<TAuthInfo = unknown>(
|
|
9
|
-
req: FastifyRequest,
|
|
10
|
-
reply: FastifyReply,
|
|
11
|
-
jwtSecret: string | undefined,
|
|
12
|
-
runMethod: (def: {
|
|
13
|
-
serviceName: string;
|
|
14
|
-
methodName: string;
|
|
15
|
-
params: unknown[];
|
|
16
|
-
http: { clientName: string; authTokenPayload?: AuthTokenPayload<TAuthInfo> };
|
|
17
|
-
}) => Promise<unknown>,
|
|
18
|
-
): Promise<void>;
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
| Parameter | Type | Description |
|
|
22
|
-
|-----------|------|-------------|
|
|
23
|
-
| `req` | `FastifyRequest` | Fastify 요청 객체 |
|
|
24
|
-
| `reply` | `FastifyReply` | Fastify 응답 객체 |
|
|
25
|
-
| `jwtSecret` | `string \| undefined` | JWT 시크릿. `Authorization` 헤더가 있는데 시크릿이 없으면 에러를 던진다 |
|
|
26
|
-
| `runMethod` | `(def) => Promise<unknown>` | 서비스 메서드 실행 콜백 |
|
|
27
|
-
|
|
28
|
-
요청 매개변수 파싱:
|
|
29
|
-
- **GET**: `?json=` 쿼리 파라미터에서 JSON 배열을 파싱한다
|
|
30
|
-
- **POST**: 요청 본문이 JSON 배열이어야 한다. 배열이 아니면 400을 반환한다
|
|
31
|
-
- **그 외**: 405 Method Not Allowed를 반환한다
|
|
32
|
-
|
|
33
|
-
인증 실패 시 401 응답을 반환한다.
|
|
34
|
-
|
|
35
|
-
## `handleUpload`
|
|
36
|
-
|
|
37
|
-
`/upload` 경로의 multipart 파일 업로드를 처리한다. 인증 필수 (Authorization 헤더 필수).
|
|
38
|
-
|
|
39
|
-
```typescript
|
|
40
|
-
async function handleUpload(
|
|
41
|
-
req: FastifyRequest,
|
|
42
|
-
reply: FastifyReply,
|
|
43
|
-
rootPath: string,
|
|
44
|
-
jwtSecret: string | undefined,
|
|
45
|
-
): Promise<void>;
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
| Parameter | Type | Description |
|
|
49
|
-
|-----------|------|-------------|
|
|
50
|
-
| `req` | `FastifyRequest` | Fastify 요청 객체 |
|
|
51
|
-
| `reply` | `FastifyReply` | Fastify 응답 객체 |
|
|
52
|
-
| `rootPath` | `string` | 서버 루트 경로. 파일은 `{rootPath}/www/uploads/`에 저장된다 |
|
|
53
|
-
| `jwtSecret` | `string \| undefined` | JWT 시크릿 |
|
|
54
|
-
|
|
55
|
-
동작:
|
|
56
|
-
- 파일명은 UUID로 변환되고 원래 확장자를 유지한다
|
|
57
|
-
- 파일 크기 제한 초과 시 에러를 던진다
|
|
58
|
-
- 에러 발생 시 불완전한 파일과 이미 저장된 파일을 모두 정리한다
|
|
59
|
-
|
|
60
|
-
응답: `ServiceUploadResult[]` (from `@simplysm/service-common`)
|
|
61
|
-
|
|
62
|
-
```typescript
|
|
63
|
-
// ServiceUploadResult 구조
|
|
64
|
-
{ path: "uploads/{uuid}.ext", filename: "원본파일명.ext", size: number }
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## `handleStaticFile`
|
|
68
|
-
|
|
69
|
-
정적 파일 서빙을 처리한다. 경로 탐색 공격 방지와 숨김 파일 접근 차단이 포함되어 있다.
|
|
70
|
-
|
|
71
|
-
```typescript
|
|
72
|
-
async function handleStaticFile(
|
|
73
|
-
req: FastifyRequest,
|
|
74
|
-
reply: FastifyReply,
|
|
75
|
-
rootPath: string,
|
|
76
|
-
urlPath: string,
|
|
77
|
-
): Promise<void>;
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
| Parameter | Type | Description |
|
|
81
|
-
|-----------|------|-------------|
|
|
82
|
-
| `req` | `FastifyRequest` | Fastify 요청 객체 |
|
|
83
|
-
| `reply` | `FastifyReply` | Fastify 응답 객체 |
|
|
84
|
-
| `rootPath` | `string` | 서버 루트 경로. `{rootPath}/www/` 하위에서 파일을 찾는다 |
|
|
85
|
-
| `urlPath` | `string` | 요청 URL 경로 (슬래시 제거됨) |
|
|
86
|
-
|
|
87
|
-
보안 처리:
|
|
88
|
-
- `{rootPath}/www/` 외부 경로 접근 시 에러를 던진다 (경로 탐색 공격 방지)
|
|
89
|
-
- `.`으로 시작하는 파일은 403 Forbidden을 반환한다
|
|
90
|
-
|
|
91
|
-
디렉토리 처리:
|
|
92
|
-
- 디렉토리 요청 시 끝에 슬래시가 없으면 슬래시를 추가하여 리다이렉트한다
|
|
93
|
-
- 디렉토리에 대해 `index.html`을 자동으로 서빙한다
|
package/docs/transport-socket.md
DELETED
|
@@ -1,119 +0,0 @@
|
|
|
1
|
-
# Transport - Socket
|
|
2
|
-
|
|
3
|
-
## `WebSocketHandler`
|
|
4
|
-
|
|
5
|
-
다중 WebSocket 연결을 관리하고, 메시지를 서비스로 라우팅하며, 이벤트 브로드캐스팅을 처리하는 인터페이스.
|
|
6
|
-
|
|
7
|
-
```typescript
|
|
8
|
-
interface WebSocketHandler {
|
|
9
|
-
addSocket(socket: WebSocket, clientId: string, clientName: string, connReq: FastifyRequest): void;
|
|
10
|
-
closeAll(): void;
|
|
11
|
-
emit<TEventDef extends ServiceEventDef>(
|
|
12
|
-
eventName: string,
|
|
13
|
-
infoSelector: (item: TEventDef["$info"]) => boolean,
|
|
14
|
-
data: TEventDef["$data"],
|
|
15
|
-
): Promise<void>;
|
|
16
|
-
}
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
| Method | Description |
|
|
20
|
-
|--------|-------------|
|
|
21
|
-
| `addSocket(socket, clientId, clientName, connReq)` | 새 WebSocket 연결을 추가한다. 동일 `clientId`의 이전 연결이 있으면 해제한다 |
|
|
22
|
-
| `closeAll()` | 모든 활성 연결을 닫는다 |
|
|
23
|
-
| `emit(eventName, infoSelector, data)` | `eventName`에 해당하는 이벤트 리스너 중 `infoSelector`에 매칭되는 클라이언트에 이벤트를 브로드캐스트한다. 제네릭 `TEventDef`로 타입 안전성 보장 |
|
|
24
|
-
|
|
25
|
-
메시지 라우팅 (`processRequest` 내부):
|
|
26
|
-
|
|
27
|
-
| `message.name` | 처리 |
|
|
28
|
-
|-----------------|------|
|
|
29
|
-
| `"SvcName.methodName"` (`.` 포함) | `runMethod`로 서비스 메서드 실행 |
|
|
30
|
-
| `"evt:add"` | 이벤트 리스너 등록 (`key`, `name`, `info`) |
|
|
31
|
-
| `"evt:remove"` | 이벤트 리스너 제거 (`key`) |
|
|
32
|
-
| `"evt:gets"` | 전체 소켓의 특정 이벤트 리스너 조회 |
|
|
33
|
-
| `"evt:emit"` | 지정된 키 대상 이벤트 발송 |
|
|
34
|
-
| `"auth"` | JWT 토큰 검증 후 소켓에 `authTokenPayload` 저장 |
|
|
35
|
-
|
|
36
|
-
## `createWebSocketHandler`
|
|
37
|
-
|
|
38
|
-
`WebSocketHandler` 인스턴스를 생성한다.
|
|
39
|
-
|
|
40
|
-
```typescript
|
|
41
|
-
function createWebSocketHandler(
|
|
42
|
-
runMethod: (def: {
|
|
43
|
-
serviceName: string;
|
|
44
|
-
methodName: string;
|
|
45
|
-
params: unknown[];
|
|
46
|
-
socket?: ServiceSocket;
|
|
47
|
-
}) => Promise<unknown>,
|
|
48
|
-
jwtSecret: string | undefined,
|
|
49
|
-
): WebSocketHandler;
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
| Parameter | Type | Description |
|
|
53
|
-
|-----------|------|-------------|
|
|
54
|
-
| `runMethod` | `(def: { serviceName, methodName, params, socket? }) => Promise<unknown>` | 서비스 메서드 실행 콜백 |
|
|
55
|
-
| `jwtSecret` | `string \| undefined` | JWT 시크릿. `undefined`이면 `auth` 메시지 처리 시 에러를 던진다 |
|
|
56
|
-
|
|
57
|
-
## `ServiceSocket`
|
|
58
|
-
|
|
59
|
-
프로토콜 인코딩/디코딩, ping/pong 연결 유지, 이벤트 리스너 추적이 포함된 단일 WebSocket 연결 인터페이스.
|
|
60
|
-
|
|
61
|
-
```typescript
|
|
62
|
-
interface ServiceSocket {
|
|
63
|
-
readonly connectedAtDateTime: DateTime;
|
|
64
|
-
readonly clientName: string;
|
|
65
|
-
readonly connReq: FastifyRequest;
|
|
66
|
-
authTokenPayload?: AuthTokenPayload;
|
|
67
|
-
|
|
68
|
-
close(): void;
|
|
69
|
-
send(uuid: string, msg: ServiceServerMessage): Promise<number>;
|
|
70
|
-
addListener(key: string, eventName: string, info: unknown): void;
|
|
71
|
-
removeListener(key: string): void;
|
|
72
|
-
getEventListeners(eventName: string): Array<{ key: string; info: unknown }>;
|
|
73
|
-
filterEventTargetKeys(targetKeys: string[]): string[];
|
|
74
|
-
on(event: "error", handler: (err: Error) => void): void;
|
|
75
|
-
on(event: "close", handler: (code: number) => void): void;
|
|
76
|
-
on(event: "message", handler: (data: { uuid: string; msg: ServiceClientMessage }) => void): void;
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
| Member | Type | Description |
|
|
81
|
-
|--------|------|-------------|
|
|
82
|
-
| `connectedAtDateTime` | `DateTime` | 연결 시점의 DateTime 객체 |
|
|
83
|
-
| `clientName` | `string` | 클라이언트 앱 이름 |
|
|
84
|
-
| `connReq` | `FastifyRequest` | 연결 시점의 Fastify 요청 객체 |
|
|
85
|
-
| `authTokenPayload` | `AuthTokenPayload` (optional) | WebSocket `auth` 메시지로 검증된 토큰 페이로드 |
|
|
86
|
-
|
|
87
|
-
| Method | Description |
|
|
88
|
-
|--------|-------------|
|
|
89
|
-
| `close()` | WebSocket 연결을 종료한다 (`socket.terminate()`) |
|
|
90
|
-
| `send(uuid, msg)` | 메시지를 프로토콜 인코딩하여 전송한다. 전송된 바이트 수를 반환한다. 소켓이 닫혀있으면 0을 반환한다 |
|
|
91
|
-
| `addListener(key, eventName, info)` | key/name/info로 이벤트 리스너를 등록한다 |
|
|
92
|
-
| `removeListener(key)` | key로 이벤트 리스너를 제거한다 |
|
|
93
|
-
| `getEventListeners(eventName)` | 특정 이벤트 이름에 해당하는 모든 리스너의 `{ key, info }` 배열을 반환한다 |
|
|
94
|
-
| `filterEventTargetKeys(targetKeys)` | 이 소켓의 리스너에 존재하는 대상 키만 필터링하여 반환한다 |
|
|
95
|
-
| `on(event, handler)` | `"error"`, `"close"`, `"message"` 이벤트 핸들러를 등록한다 |
|
|
96
|
-
|
|
97
|
-
ping/pong: 5초 간격으로 ping을 전송하고, 클라이언트의 `0x01` 바이트(ping) 수신 시 `0x02` 바이트(pong)를 응답한다. pong 미수신 시 연결을 종료한다.
|
|
98
|
-
|
|
99
|
-
프로토콜 메시지 처리: `createServerProtocolWrapper`를 사용하여 메시지를 인코딩/디코딩한다. 수신 메시지의 디코딩 결과가 `"progress"` 타입이면 진행률 메시지를 클라이언트에 전송한다.
|
|
100
|
-
|
|
101
|
-
## `createServiceSocket`
|
|
102
|
-
|
|
103
|
-
`ServiceSocket` 인스턴스를 생성한다.
|
|
104
|
-
|
|
105
|
-
```typescript
|
|
106
|
-
function createServiceSocket(
|
|
107
|
-
socket: WebSocket,
|
|
108
|
-
clientId: string,
|
|
109
|
-
clientName: string,
|
|
110
|
-
connReq: FastifyRequest,
|
|
111
|
-
): ServiceSocket;
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
| Parameter | Type | Description |
|
|
115
|
-
|-----------|------|-------------|
|
|
116
|
-
| `socket` | `WebSocket` | 기저 WebSocket 인스턴스 (`ws` 라이브러리) |
|
|
117
|
-
| `clientId` | `string` | 클라이언트 고유 식별자 |
|
|
118
|
-
| `clientName` | `string` | 클라이언트 앱 이름 |
|
|
119
|
-
| `connReq` | `FastifyRequest` | 연결 시점의 Fastify 요청 객체 |
|