@simplysm/service-client 14.0.49 → 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 +69 -154
- package/dist/features/event-client.js +1 -1
- package/dist/features/event-client.js.map +1 -1
- package/dist/transport/service-transport.js.map +1 -1
- package/docs/features/event-client.md +88 -0
- package/docs/features/file-client.md +58 -0
- package/docs/features/orm-client-connector.md +83 -0
- package/docs/features/orm-client-db-context-executor.md +26 -0
- package/docs/{main.md → main/service-client.md} +50 -51
- package/docs/{protocol.md → protocol/client-protocol-wrapper.md} +19 -16
- package/docs/transport/service-transport.md +63 -0
- package/docs/transport/socket-provider.md +95 -0
- package/docs/types/blob-input.md +7 -0
- package/docs/types/browser-worker.md +47 -0
- package/docs/types/file-collection.md +21 -0
- package/docs/types/service-connection-options.md +22 -0
- package/docs/types/service-progress.md +39 -0
- package/package.json +4 -4
- package/src/features/event-client.ts +1 -1
- package/src/transport/service-transport.ts +2 -2
- package/docs/features.md +0 -217
- package/docs/transport.md +0 -131
- package/docs/types.md +0 -93
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ServiceClient
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
WebSocket 기반 서비스 클라이언트의 최상위 파사드 클래스. `SocketProvider`, `ClientProtocolWrapper`, `ServiceTransport`, `EventClient`, `FileClient`를 내부적으로 조합한다.
|
|
3
|
+
WebSocket 기반 서비스 클라이언트의 최상위 파사드 클래스. [`SocketProvider`](../transport/socket-provider.md), [`ClientProtocolWrapper`](../protocol/client-protocol-wrapper.md), [`ServiceTransport`](../transport/service-transport.md), [`EventClient`](../features/event-client.md), [`FileClient`](../features/file-client.md)를 내부적으로 조합한다.
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
6
|
export class ServiceClient extends EventEmitter<ServiceClientEvents> {
|
|
@@ -14,34 +12,63 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
|
|
|
14
12
|
get hostUrl(): string;
|
|
15
13
|
getService<TService>(serviceName: string): ServiceProxy<TService>;
|
|
16
14
|
getEvent<TEventDef extends ServiceEventDef>(eventName: string): ClientEventProxy<TEventDef>;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
connect(): Promise<void>;
|
|
16
|
+
close(): Promise<void>;
|
|
17
|
+
send(
|
|
20
18
|
serviceName: string,
|
|
21
19
|
methodName: string,
|
|
22
20
|
params: unknown[],
|
|
23
21
|
progress?: ServiceProgress,
|
|
24
22
|
): Promise<unknown>;
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
auth(token: string): Promise<void>;
|
|
24
|
+
addListener<TEventDef extends ServiceEventDef>(
|
|
27
25
|
eventName: string,
|
|
28
26
|
info: TEventDef["$info"],
|
|
29
27
|
cb: (data: TEventDef["$data"]) => PromiseLike<void>,
|
|
30
28
|
): Promise<string>;
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
removeListener(key: string): Promise<void>;
|
|
30
|
+
emitEvent<TEventDef extends ServiceEventDef>(
|
|
33
31
|
eventName: string,
|
|
34
32
|
infoSelector: (item: TEventDef["$info"]) => boolean,
|
|
35
33
|
data: TEventDef["$data"],
|
|
36
34
|
): Promise<void>;
|
|
37
|
-
|
|
35
|
+
uploadFile(
|
|
38
36
|
files: File[] | FileCollection | { name: string; data: BlobInput }[],
|
|
39
37
|
): Promise<ServiceUploadResult[]>;
|
|
40
|
-
|
|
38
|
+
downloadFileBuffer(relPath: string): Promise<Bytes>;
|
|
41
39
|
}
|
|
42
40
|
```
|
|
43
41
|
|
|
44
|
-
|
|
42
|
+
## Constructor Parameters
|
|
43
|
+
|
|
44
|
+
| Parameter | Type | Description |
|
|
45
|
+
|-----------|------|-------------|
|
|
46
|
+
| `name` | `string` | 클라이언트 식별자 (WebSocket URL 파라미터 및 HTTP 헤더로 전달됨) |
|
|
47
|
+
| `options` | [`ServiceConnectionOptions`](../types/service-connection-options.md) | 연결 옵션 |
|
|
48
|
+
|
|
49
|
+
## Members
|
|
50
|
+
|
|
51
|
+
| Member | Kind | Type | Description |
|
|
52
|
+
|--------|------|------|-------------|
|
|
53
|
+
| `name` | property | `string` | 클라이언트 식별자 (읽기 전용) |
|
|
54
|
+
| `options` | property | `ServiceConnectionOptions` | 연결 옵션 (읽기 전용) |
|
|
55
|
+
| `connected` | getter | `boolean` | 현재 WebSocket 연결 상태 |
|
|
56
|
+
| `hostUrl` | getter | `string` | HTTP 기본 URL (`http(s)://host:port`) |
|
|
57
|
+
| `connect()` | method | `Promise<void>` | WebSocket 연결 시작 |
|
|
58
|
+
| `close()` | method | `Promise<void>` | WebSocket 연결 종료 및 protocol dispose |
|
|
59
|
+
| `auth(token)` | method | `Promise<void>` | 서버에 인증 토큰 전송. 재연결 시 자동 재인증됨 |
|
|
60
|
+
| `getService<TService>(serviceName)` | method | `ServiceProxy<TService>` | 타입 안전한 서비스 프록시 반환 |
|
|
61
|
+
| `getEvent<TEventDef>(eventName)` | method | `ClientEventProxy<TEventDef>` | 타입 안전한 이벤트 프록시 반환 |
|
|
62
|
+
| `send(serviceName, methodName, params, progress?)` | method | `Promise<unknown>` | 서비스 메서드 원격 호출 |
|
|
63
|
+
| `addListener(eventName, info, cb)` | method | `Promise<string>` | 서버 이벤트 구독. 연결 상태여야 함 |
|
|
64
|
+
| `removeListener(key)` | method | `Promise<void>` | 서버 이벤트 구독 해제 |
|
|
65
|
+
| `emitEvent(eventName, infoSelector, data)` | method | `Promise<void>` | 서버 이벤트 발행 |
|
|
66
|
+
| `uploadFile(files)` | method | `Promise<ServiceUploadResult[]>` | 파일 업로드. `auth()` 호출 후 사용해야 함 |
|
|
67
|
+
| `downloadFileBuffer(relPath)` | method | `Promise<Bytes>` | 파일 다운로드 (`Uint8Array` 반환) |
|
|
68
|
+
|
|
69
|
+
## Events (EventEmitter)
|
|
70
|
+
|
|
71
|
+
`ServiceClient`는 `EventEmitter<ServiceClientEvents>`를 상속한다. `.on()`/`.off()`로 이벤트를 구독한다.
|
|
45
72
|
|
|
46
73
|
| Event | Data Type | Description |
|
|
47
74
|
|-------|-----------|-------------|
|
|
@@ -50,39 +77,11 @@ export class ServiceClient extends EventEmitter<ServiceClientEvents> {
|
|
|
50
77
|
| `server-progress` | `ServiceProgressState` | 서버 내부 처리 progress |
|
|
51
78
|
| `state` | `"connected" \| "closed" \| "reconnecting"` | 연결 상태 변경 |
|
|
52
79
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
| Parameter | Type | Description |
|
|
56
|
-
|-----------|------|-------------|
|
|
57
|
-
| `name` | `string` | 클라이언트 식별자 (WebSocket URL 파라미터 및 HTTP 헤더로 전달됨) |
|
|
58
|
-
| `options` | `ServiceConnectionOptions` | 연결 옵션 (host, port, ssl, maxReconnectCount) |
|
|
59
|
-
|
|
60
|
-
주요 메서드:
|
|
61
|
-
|
|
62
|
-
| Method | Description |
|
|
63
|
-
|--------|-------------|
|
|
64
|
-
| `connect()` | WebSocket 연결 시작 |
|
|
65
|
-
| `close()` | WebSocket 연결 종료 및 protocol dispose |
|
|
66
|
-
| `auth(token)` | 서버에 인증 토큰 전송. 재연결 시 자동 재인증됨 |
|
|
67
|
-
| `getService<TService>(serviceName)` | 타입 안전한 서비스 프록시 반환 |
|
|
68
|
-
| `getEvent<TEventDef>(eventName)` | 타입 안전한 이벤트 프록시 반환. `ClientEventProxy<TEventDef>`를 반환하며 이벤트 이름과 타입이 캡처됨 |
|
|
69
|
-
| `send(serviceName, methodName, params, progress?)` | 서비스 메서드 원격 호출 |
|
|
70
|
-
| `addListener(eventName, info, cb)` | 서버 이벤트 구독. 제네릭 `TEventDef`로 타입 안전성 보장. 연결 상태여야 함 |
|
|
71
|
-
| `removeListener(key)` | 서버 이벤트 구독 해제 |
|
|
72
|
-
| `emitEvent(eventName, infoSelector, data)` | 서버 이벤트 발행. 제네릭 `TEventDef`로 타입 안전성 보장 |
|
|
73
|
-
| `uploadFile(files)` | 파일 업로드. `auth()` 호출 후 사용해야 함 |
|
|
74
|
-
| `downloadFileBuffer(relPath)` | 파일 다운로드 (`Uint8Array` 반환) |
|
|
75
|
-
|
|
76
|
-
접근자:
|
|
77
|
-
|
|
78
|
-
| Property | Type | Description |
|
|
79
|
-
|----------|------|-------------|
|
|
80
|
-
| `connected` | `boolean` | 현재 WebSocket 연결 상태 |
|
|
81
|
-
| `hostUrl` | `string` | HTTP 기본 URL (`http(s)://host:port`) |
|
|
80
|
+
## Related Types
|
|
82
81
|
|
|
83
|
-
|
|
82
|
+
### `ServiceProxy`
|
|
84
83
|
|
|
85
|
-
`TService`의 모든 메서드 반환 타입을 `Promise`로 래핑하는 유틸리티 타입. `
|
|
84
|
+
`TService`의 모든 메서드 반환 타입을 `Promise`로 래핑하는 유틸리티 타입. `getService<TService>()`의 반환 타입으로 사용된다.
|
|
86
85
|
|
|
87
86
|
```typescript
|
|
88
87
|
export type ServiceProxy<TService> = {
|
|
@@ -107,7 +106,7 @@ export function createServiceClient(name: string, options: ServiceConnectionOpti
|
|
|
107
106
|
| `name` | `string` | 클라이언트 식별자 |
|
|
108
107
|
| `options` | `ServiceConnectionOptions` | 연결 옵션 |
|
|
109
108
|
|
|
110
|
-
|
|
109
|
+
## Usage
|
|
111
110
|
|
|
112
111
|
```typescript
|
|
113
112
|
import { ServiceClient } from "@simplysm/service-client";
|
|
@@ -126,16 +125,16 @@ await client.auth("my-auth-token");
|
|
|
126
125
|
const userSvc = client.getService<UserService>("User");
|
|
127
126
|
const users = await userSvc.getList();
|
|
128
127
|
|
|
129
|
-
// 이벤트 프록시
|
|
128
|
+
// 이벤트 프록시 (권장)
|
|
130
129
|
const chatEvt = client.getEvent<typeof ChatEvent>("Chat");
|
|
131
130
|
const key = await chatEvt.addListener({ roomId: "room-1" }, async (data) => {
|
|
132
|
-
|
|
131
|
+
console.log("메시지:", data.message);
|
|
133
132
|
});
|
|
134
133
|
await chatEvt.removeListener(key);
|
|
135
134
|
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
//
|
|
135
|
+
// 연결 상태 모니터링
|
|
136
|
+
client.on("state", (state) => {
|
|
137
|
+
console.log("상태:", state); // "connected" | "closed" | "reconnecting"
|
|
139
138
|
});
|
|
140
139
|
|
|
141
140
|
// 파일 업로드
|
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ClientProtocolWrapper
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
메시지 인코딩/디코딩 인터페이스. 데이터 크기가 30KB 이상이면 Web Worker로 처리를 오프로드한다.
|
|
3
|
+
메시지 인코딩/디코딩 인터페이스. 데이터 크기가 30KB 이상이면 Web Worker로 처리를 오프로드한다. 팩토리 함수 `createClientProtocolWrapper`로 생성한다.
|
|
6
4
|
|
|
7
5
|
```typescript
|
|
8
6
|
export interface ClientProtocolWrapper {
|
|
@@ -12,11 +10,13 @@ export interface ClientProtocolWrapper {
|
|
|
12
10
|
}
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
|
18
|
-
|
|
19
|
-
| `
|
|
13
|
+
## Members
|
|
14
|
+
|
|
15
|
+
| Member | Kind | Type | Description |
|
|
16
|
+
|--------|------|------|-------------|
|
|
17
|
+
| `encode(uuid, message)` | method | `Promise<{ chunks: Bytes[]; totalSize: number }>` | 메시지를 바이너리 청크로 인코딩. 큰 데이터는 Worker로 오프로드 |
|
|
18
|
+
| `decode(bytes)` | method | `Promise<ServiceMessageDecodeResult<ServiceMessage>>` | 바이너리를 메시지로 디코딩. 큰 데이터는 Worker로 오프로드 (zero-copy 전송) |
|
|
19
|
+
| `dispose()` | method | `void` | 내부 `ServiceProtocol`과 Worker resolver 맵 정리 |
|
|
20
20
|
|
|
21
21
|
## `createClientProtocolWrapper`
|
|
22
22
|
|
|
@@ -32,15 +32,16 @@ export function createClientProtocolWrapper(protocol: ServiceProtocol): ClientPr
|
|
|
32
32
|
|
|
33
33
|
내부 동작:
|
|
34
34
|
- 임계값: 30KB (`30 * 1024` bytes)
|
|
35
|
-
- Worker가 지원되지 않는
|
|
36
|
-
- Worker는
|
|
35
|
+
- Worker가 지원되지 않는 환경에서는 메인 스레드에서 처리
|
|
36
|
+
- Worker는 모듈 스코프 싱글턴으로 공유됨 (`createClientProtocolWrapper` 여러 번 호출해도 Worker는 하나)
|
|
37
37
|
- Worker 작업은 60초 타임아웃 후 자동 reject (메모리 누수 방지)
|
|
38
|
-
- `decode` 시 Worker로 `ArrayBuffer` 소유권 이전 (zero-copy)
|
|
38
|
+
- `decode` 시 Worker로 `ArrayBuffer` 소유권 이전 (zero-copy)
|
|
39
|
+
|
|
40
|
+
**Worker 생성 패턴 제약:**
|
|
39
41
|
|
|
40
|
-
|
|
41
|
-
Worker 생성 시 반드시 `new Worker(new URL("...", import.meta.url))` 직접 패턴을 사용해야 한다. sd-cli의 esbuild Worker 번들링 플러그인(`sd-worker-bundle`)이 AST에서 `new Worker(new URL(..., import.meta.url))` 패턴만 인식하여 Worker 파일을 별도 번들로 분리한다. 래퍼 함수(예: `createBrowserWorker(new URL(...))`)로 감싸면 `CallExpression`이 되어 플러그인이 인식하지 못하고, Worker 파일이 번들에 포함되지 않아 브라우저에서 404 에러가 발생한다.
|
|
42
|
+
Worker 생성 시 반드시 `new Worker(new URL("...", import.meta.url))` 직접 패턴을 사용해야 한다. sd-cli의 esbuild Worker 번들링 플러그인(`sd-worker-bundle`)이 AST에서 이 패턴만 인식하여 Worker 파일을 별도 번들로 분리한다. 래퍼 함수로 감싸면 플러그인이 인식하지 못하고 브라우저에서 404 에러가 발생한다.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
+
## Usage
|
|
44
45
|
|
|
45
46
|
```typescript
|
|
46
47
|
import { createClientProtocolWrapper } from "@simplysm/service-client";
|
|
@@ -50,7 +51,9 @@ const protocol = createServiceProtocol();
|
|
|
50
51
|
const wrapper = createClientProtocolWrapper(protocol);
|
|
51
52
|
|
|
52
53
|
const { chunks, totalSize } = await wrapper.encode("uuid-1", { name: "User.getList", body: [] });
|
|
54
|
+
// chunks: Bytes[] — 전송할 청크 배열
|
|
55
|
+
// totalSize: number — 전체 크기 (progress 표시용)
|
|
53
56
|
|
|
54
|
-
// 사용 완료 후 정리
|
|
57
|
+
// 사용 완료 후 반드시 정리
|
|
55
58
|
wrapper.dispose();
|
|
56
59
|
```
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# ServiceTransport
|
|
2
|
+
|
|
3
|
+
요청-응답 매핑, progress 중계, 서버 이벤트 디스패치를 담당하는 인터페이스. 팩토리 함수 `createServiceTransport`로 생성한다.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export interface ServiceTransport {
|
|
7
|
+
on<K extends keyof ServiceTransportEvents & string>(
|
|
8
|
+
type: K,
|
|
9
|
+
listener: (data: ServiceTransportEvents[K]) => void,
|
|
10
|
+
): void;
|
|
11
|
+
off<K extends keyof ServiceTransportEvents & string>(
|
|
12
|
+
type: K,
|
|
13
|
+
listener: (data: ServiceTransportEvents[K]) => void,
|
|
14
|
+
): void;
|
|
15
|
+
send(message: ServiceClientMessage, progress?: ServiceProgress): Promise<unknown>;
|
|
16
|
+
}
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Members
|
|
20
|
+
|
|
21
|
+
| Member | Kind | Type | Description |
|
|
22
|
+
|--------|------|------|-------------|
|
|
23
|
+
| `on(type, listener)` | method | `void` | 이벤트 리스너 등록 |
|
|
24
|
+
| `off(type, listener)` | method | `void` | 이벤트 리스너 제거 |
|
|
25
|
+
| `send(message, progress?)` | method | `Promise<unknown>` | 서버에 메시지 전송하고 응답 대기 |
|
|
26
|
+
|
|
27
|
+
## Related Types
|
|
28
|
+
|
|
29
|
+
### `ServiceTransportEvents`
|
|
30
|
+
|
|
31
|
+
`ServiceTransport`에서 발생하는 이벤트 타입 맵.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
export interface ServiceTransportEvents {
|
|
35
|
+
event: { keys: string[]; data: unknown };
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
| Event | Type | Description |
|
|
40
|
+
|-------|------|-------------|
|
|
41
|
+
| `event` | `{ keys: string[]; data: unknown }` | 서버에서 발행된 이벤트 수신 |
|
|
42
|
+
|
|
43
|
+
## `createServiceTransport`
|
|
44
|
+
|
|
45
|
+
`ServiceTransport` 팩토리 함수.
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
export function createServiceTransport(
|
|
49
|
+
socket: SocketProvider,
|
|
50
|
+
protocol: ClientProtocolWrapper,
|
|
51
|
+
): ServiceTransport;
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
| Parameter | Type | Description |
|
|
55
|
+
|-----------|------|-------------|
|
|
56
|
+
| `socket` | [`SocketProvider`](./socket-provider.md) | WebSocket 소켓 제공자 |
|
|
57
|
+
| `protocol` | [`ClientProtocolWrapper`](../protocol/client-protocol-wrapper.md) | 인코딩/디코딩 래퍼 |
|
|
58
|
+
|
|
59
|
+
내부 동작:
|
|
60
|
+
- `uuid` 기반 요청-응답 `Map`으로 비동기 응답을 매핑
|
|
61
|
+
- 소켓 `closed`/`reconnecting` 상태 시 대기 중인 모든 요청을 reject
|
|
62
|
+
- 분할 메시지의 progress 상태를 추적하여 완료 시 100% 이벤트 전송
|
|
63
|
+
- 소켓 `message` 이벤트를 수신하여 `progress`, `response`, `error`, `evt:on` 타입으로 분기 처리
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# SocketProvider
|
|
2
|
+
|
|
3
|
+
WebSocket 연결, 재연결, 하트비트를 관리하는 인터페이스. 팩토리 함수 `createSocketProvider`로 생성한다.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export interface SocketProvider {
|
|
7
|
+
readonly clientName: string;
|
|
8
|
+
readonly connected: boolean;
|
|
9
|
+
on<K extends keyof SocketProviderEvents & string>(
|
|
10
|
+
type: K,
|
|
11
|
+
listener: (data: SocketProviderEvents[K]) => void,
|
|
12
|
+
): void;
|
|
13
|
+
off<K extends keyof SocketProviderEvents & string>(
|
|
14
|
+
type: K,
|
|
15
|
+
listener: (data: SocketProviderEvents[K]) => void,
|
|
16
|
+
): void;
|
|
17
|
+
connect(): Promise<void>;
|
|
18
|
+
close(): Promise<void>;
|
|
19
|
+
send(data: Bytes): Promise<void>;
|
|
20
|
+
}
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Members
|
|
24
|
+
|
|
25
|
+
| Member | Kind | Type | Description |
|
|
26
|
+
|--------|------|------|-------------|
|
|
27
|
+
| `clientName` | property | `string` | 클라이언트 식별자 (읽기 전용) |
|
|
28
|
+
| `connected` | getter | `boolean` | 현재 연결 상태 |
|
|
29
|
+
| `on(type, listener)` | method | `void` | 이벤트 리스너 등록 |
|
|
30
|
+
| `off(type, listener)` | method | `void` | 이벤트 리스너 제거 |
|
|
31
|
+
| `connect()` | method | `Promise<void>` | WebSocket 연결 시작 |
|
|
32
|
+
| `close()` | method | `Promise<void>` | WebSocket 연결 종료 |
|
|
33
|
+
| `send(data)` | method | `Promise<void>` | 바이너리 데이터 전송. 연결 복구 대기 후 전송 |
|
|
34
|
+
|
|
35
|
+
## Related Types
|
|
36
|
+
|
|
37
|
+
### `SocketProviderEvents`
|
|
38
|
+
|
|
39
|
+
`SocketProvider`에서 발생하는 이벤트 타입 맵.
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
export interface SocketProviderEvents {
|
|
43
|
+
message: Bytes;
|
|
44
|
+
state: "connected" | "closed" | "reconnecting";
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
| Event | Type | Description |
|
|
49
|
+
|-------|------|-------------|
|
|
50
|
+
| `message` | `Bytes` | 서버로부터 바이너리 메시지 수신 |
|
|
51
|
+
| `state` | `"connected" \| "closed" \| "reconnecting"` | 연결 상태 변경 |
|
|
52
|
+
|
|
53
|
+
## `createSocketProvider`
|
|
54
|
+
|
|
55
|
+
`SocketProvider` 팩토리 함수.
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
export function createSocketProvider(
|
|
59
|
+
url: string,
|
|
60
|
+
clientName: string,
|
|
61
|
+
maxReconnectCount: number,
|
|
62
|
+
): SocketProvider;
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
| Parameter | Type | Description |
|
|
66
|
+
|-----------|------|-------------|
|
|
67
|
+
| `url` | `string` | WebSocket 서버 URL (`ws://` 또는 `wss://`) |
|
|
68
|
+
| `clientName` | `string` | 클라이언트 식별자 (URL 파라미터 `clientName`으로 전달됨) |
|
|
69
|
+
| `maxReconnectCount` | `number` | 최대 재연결 횟수. `0`이면 재연결 안 함 |
|
|
70
|
+
|
|
71
|
+
내부 동작:
|
|
72
|
+
- 하트비트: 5초마다 ping 전송, 30초 응답 없으면 재연결 시도
|
|
73
|
+
- 재연결: 연결 끊김 시 3초 간격으로 `maxReconnectCount`회 재시도
|
|
74
|
+
- Node.js 환경에서 `globalThis.WebSocket`이 없으면 `ws` 패키지로 폴리필
|
|
75
|
+
- ping/pong은 1바이트 패킷(ping: `0x01`, pong: `0x02`)으로 처리
|
|
76
|
+
|
|
77
|
+
## Usage
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { createSocketProvider } from "@simplysm/service-client";
|
|
81
|
+
|
|
82
|
+
const socket = createSocketProvider("ws://localhost:3000/ws", "my-app", 10);
|
|
83
|
+
|
|
84
|
+
socket.on("state", (state) => {
|
|
85
|
+
console.log("소켓 상태:", state); // "connected" | "closed" | "reconnecting"
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
socket.on("message", (bytes) => {
|
|
89
|
+
console.log("메시지 수신:", bytes.length, "bytes");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
await socket.connect();
|
|
93
|
+
await socket.send(new Uint8Array([1, 2, 3]));
|
|
94
|
+
await socket.close();
|
|
95
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# BrowserWorker
|
|
2
|
+
|
|
3
|
+
Web Worker 최소 인터페이스. DOM lib 없이도 타입체크가 통과하도록 하는 추상 타입이다.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export interface BrowserWorker {
|
|
7
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
8
|
+
onerror: ((event: Event) => void) | null;
|
|
9
|
+
postMessage(message: unknown, transfer?: unknown[]): void;
|
|
10
|
+
terminate(): void;
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Fields
|
|
15
|
+
|
|
16
|
+
| Field | Type | Description |
|
|
17
|
+
|-------|------|-------------|
|
|
18
|
+
| `onmessage` | `((event: MessageEvent) => void) \| null` | 메시지 수신 핸들러 |
|
|
19
|
+
| `onerror` | `((event: Event) => void) \| null` | 에러 핸들러 |
|
|
20
|
+
| `postMessage(message, transfer?)` | `void` | Worker에 메시지 전송 |
|
|
21
|
+
| `terminate()` | `void` | Worker 종료 |
|
|
22
|
+
|
|
23
|
+
## Related Functions
|
|
24
|
+
|
|
25
|
+
### `isBrowserWorkerSupported`
|
|
26
|
+
|
|
27
|
+
DOM Worker API 지원 여부를 확인한다. `"Worker" in globalThis`로 판별한다.
|
|
28
|
+
|
|
29
|
+
```typescript
|
|
30
|
+
export function isBrowserWorkerSupported(): boolean;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### `isNodeWorkerSupported`
|
|
34
|
+
|
|
35
|
+
Node.js `worker_threads` 지원 여부를 확인한다. `process.versions.node` 존재 여부로 판별한다.
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
export function isNodeWorkerSupported(): boolean;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### `isWorkerSupported`
|
|
42
|
+
|
|
43
|
+
Worker 오프로딩 지원 여부를 확인한다. `isBrowserWorkerSupported() || isNodeWorkerSupported()`를 반환한다.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
export function isWorkerSupported(): boolean;
|
|
47
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# FileCollection
|
|
2
|
+
|
|
3
|
+
File 컬렉션 인터페이스. DOM `FileList`를 대체하며 브라우저 `FileList`와 구조적으로 호환된다.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export interface FileCollection {
|
|
7
|
+
readonly length: number;
|
|
8
|
+
item(index: number): File | null;
|
|
9
|
+
[index: number]: File;
|
|
10
|
+
[Symbol.iterator](): IterableIterator<File>;
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Fields
|
|
15
|
+
|
|
16
|
+
| Field | Type | Description |
|
|
17
|
+
|-------|------|-------------|
|
|
18
|
+
| `length` | `number` | 파일 개수 (읽기 전용) |
|
|
19
|
+
| `item(index)` | `File \| null` | 인덱스로 File 반환 |
|
|
20
|
+
| `[index]` | `File` | 인덱스 접근자 |
|
|
21
|
+
| `[Symbol.iterator]()` | `IterableIterator<File>` | for-of 이터레이션 지원 |
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ServiceConnectionOptions
|
|
2
|
+
|
|
3
|
+
서비스 서버에 연결할 때 사용하는 옵션 인터페이스.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export interface ServiceConnectionOptions {
|
|
7
|
+
port: number;
|
|
8
|
+
host: string;
|
|
9
|
+
ssl?: boolean;
|
|
10
|
+
/** 0으로 설정하면 재연결을 비활성화하고 즉시 연결을 끊음 */
|
|
11
|
+
maxReconnectCount?: number;
|
|
12
|
+
}
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Fields
|
|
16
|
+
|
|
17
|
+
| Field | Type | Required | Description |
|
|
18
|
+
|-------|------|----------|-------------|
|
|
19
|
+
| `port` | `number` | required | 서버 포트 번호 |
|
|
20
|
+
| `host` | `string` | required | 서버 호스트 주소 |
|
|
21
|
+
| `ssl` | `boolean` | optional | HTTPS/WSS 사용 여부. 기본값 `false` |
|
|
22
|
+
| `maxReconnectCount` | `number` | optional | 최대 재연결 횟수. `0`이면 재연결 비활성화. `ServiceClient` 기본값 `10` |
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# ServiceProgress
|
|
2
|
+
|
|
3
|
+
요청/응답/서버 단계별 progress 콜백을 담는 컨테이너 인터페이스.
|
|
4
|
+
|
|
5
|
+
```typescript
|
|
6
|
+
export interface ServiceProgress {
|
|
7
|
+
request?: (s: ServiceProgressState) => void;
|
|
8
|
+
response?: (s: ServiceProgressState) => void;
|
|
9
|
+
server?: (s: ServiceProgressState) => void;
|
|
10
|
+
}
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Fields
|
|
14
|
+
|
|
15
|
+
| Field | Type | Required | Description |
|
|
16
|
+
|-------|------|----------|-------------|
|
|
17
|
+
| `request` | `(s: ServiceProgressState) => void` | optional | 클라이언트 → 서버 전송 progress |
|
|
18
|
+
| `response` | `(s: ServiceProgressState) => void` | optional | 서버 → 클라이언트 수신 progress |
|
|
19
|
+
| `server` | `(s: ServiceProgressState) => void` | optional | 서버 내부 처리 progress |
|
|
20
|
+
|
|
21
|
+
## Related Types
|
|
22
|
+
|
|
23
|
+
### `ServiceProgressState`
|
|
24
|
+
|
|
25
|
+
progress 콜백에 전달되는 상태 객체.
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
export interface ServiceProgressState {
|
|
29
|
+
uuid: string;
|
|
30
|
+
totalSize: number;
|
|
31
|
+
completedSize: number;
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
| Field | Type | Description |
|
|
36
|
+
|-------|------|-------------|
|
|
37
|
+
| `uuid` | `string` | 요청 식별자 |
|
|
38
|
+
| `totalSize` | `number` | 전체 크기 (bytes) |
|
|
39
|
+
| `completedSize` | `number` | 완료된 크기 (bytes) |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simplysm/service-client",
|
|
3
|
-
"version": "14.0.
|
|
3
|
+
"version": "14.0.51",
|
|
4
4
|
"description": "심플리즘 패키지 - 서비스 (client)",
|
|
5
5
|
"author": "심플리즘",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -20,9 +20,9 @@
|
|
|
20
20
|
"sideEffects": false,
|
|
21
21
|
"dependencies": {
|
|
22
22
|
"consola": "^3.4.2",
|
|
23
|
-
"@simplysm/core-common": "14.0.
|
|
24
|
-
"@simplysm/service-common": "14.0.
|
|
25
|
-
"@simplysm/orm-common": "14.0.
|
|
23
|
+
"@simplysm/core-common": "14.0.51",
|
|
24
|
+
"@simplysm/service-common": "14.0.51",
|
|
25
|
+
"@simplysm/orm-common": "14.0.51"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@types/ws": "^8.18.1",
|
|
@@ -120,7 +120,7 @@ export function createServiceTransport(
|
|
|
120
120
|
});
|
|
121
121
|
} else {
|
|
122
122
|
if (decoded.message.name === "progress") {
|
|
123
|
-
const body = decoded.message.body
|
|
123
|
+
const body = decoded.message.body;
|
|
124
124
|
listenerInfo?.progress?.server?.({
|
|
125
125
|
uuid: decoded.uuid,
|
|
126
126
|
totalSize: body.totalSize,
|
|
@@ -151,7 +151,7 @@ export function createServiceTransport(
|
|
|
151
151
|
|
|
152
152
|
listenerInfo?.reject(toError(decoded.message.body));
|
|
153
153
|
} else if (decoded.message.name === "evt:on") {
|
|
154
|
-
const body = decoded.message.body
|
|
154
|
+
const body = decoded.message.body;
|
|
155
155
|
emitter.emit("event", { keys: body.keys, data: body.data });
|
|
156
156
|
} else {
|
|
157
157
|
throw new Error("서버로부터 잘못된 메시지를 수신했습니다.");
|