@simplysm/service-client 14.0.47 → 14.0.49
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 +200 -0
- package/dist/protocol/client-protocol-wrapper.d.ts.map +1 -1
- package/dist/protocol/client-protocol-wrapper.js +94 -51
- package/dist/protocol/client-protocol-wrapper.js.map +1 -1
- package/dist/types/browser-compat.d.ts +12 -1
- package/dist/types/browser-compat.d.ts.map +1 -1
- package/dist/types/browser-compat.js +11 -2
- package/dist/types/browser-compat.js.map +1 -1
- package/dist/workers/client-protocol.worker.js +33 -22
- package/dist/workers/client-protocol.worker.js.map +1 -1
- package/docs/features.md +217 -0
- package/docs/main.md +148 -0
- package/docs/protocol.md +56 -0
- package/docs/transport.md +131 -0
- package/docs/types.md +93 -0
- package/package.json +6 -5
- package/src/protocol/client-protocol-wrapper.ts +125 -68
- package/src/types/browser-compat.ts +24 -2
- package/src/types/node-worker-compat.d.ts +23 -0
- package/src/workers/client-protocol.worker.ts +39 -27
package/README.md
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
# @simplysm/service-client
|
|
2
|
+
|
|
3
|
+
WebSocket 기반 서비스 서버 클라이언트로, 서비스 호출, 실시간 이벤트 구독/발행, 파일 업로드/다운로드, ORM 원격 실행을 지원한다.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @simplysm/service-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## API Overview
|
|
12
|
+
|
|
13
|
+
### Core Client
|
|
14
|
+
|
|
15
|
+
| API | Type | Description |
|
|
16
|
+
|-----|------|-------------|
|
|
17
|
+
| `ServiceClient` | class | WebSocket 서비스 클라이언트 최상위 파사드 (연결, RPC, 이벤트, 파일, ORM 통합) |
|
|
18
|
+
| `createServiceClient()` | function | ServiceClient 인스턴스 생성 팩토리 함수 |
|
|
19
|
+
| `ServiceProxy<T>` | type | 서비스 메서드 반환 타입을 Promise로 래핑하는 타입 변환기 |
|
|
20
|
+
|
|
21
|
+
→ See [docs/main.md](./docs/main.md) for details.
|
|
22
|
+
|
|
23
|
+
### Transport Layer
|
|
24
|
+
|
|
25
|
+
| API | Type | Description |
|
|
26
|
+
|-----|------|-------------|
|
|
27
|
+
| `SocketProvider` | interface | WebSocket 래퍼 인터페이스 (connect, close, send, on, off, heartbeat) |
|
|
28
|
+
| `createSocketProvider()` | function | SocketProvider 팩토리. 재연결 및 하트비트 관리 |
|
|
29
|
+
| `SocketProviderEvents` | interface | SocketProvider 이벤트 맵 (message, state) |
|
|
30
|
+
| `ServiceTransport` | interface | 요청-응답 매핑, progress 중계, 이벤트 디스패치 |
|
|
31
|
+
| `createServiceTransport()` | function | ServiceTransport 팩토리. SocketProvider와 ClientProtocolWrapper 조합 |
|
|
32
|
+
| `ServiceTransportEvents` | interface | ServiceTransport 이벤트 맵 (event) |
|
|
33
|
+
|
|
34
|
+
→ See [docs/transport.md](./docs/transport.md) for details.
|
|
35
|
+
|
|
36
|
+
### Protocol & Encoding
|
|
37
|
+
|
|
38
|
+
| API | Type | Description |
|
|
39
|
+
|-----|------|-------------|
|
|
40
|
+
| `ClientProtocolWrapper` | interface | 메시지 인코딩/디코딩 (30KB 이상 시 Web Worker 오프로드) |
|
|
41
|
+
| `createClientProtocolWrapper()` | function | ClientProtocolWrapper 팩토리. ServiceProtocol 래핑 |
|
|
42
|
+
|
|
43
|
+
→ See [docs/protocol.md](./docs/protocol.md) for details.
|
|
44
|
+
|
|
45
|
+
### Event Management
|
|
46
|
+
|
|
47
|
+
| API | Type | Description |
|
|
48
|
+
|-----|------|-------------|
|
|
49
|
+
| `EventClient` | interface | 서버 이벤트 구독/발행 관리 (재연결 시 자동 재구독) |
|
|
50
|
+
| `createEventClient()` | function | EventClient 팩토리. ServiceTransport 사용 |
|
|
51
|
+
| `ClientEventProxy<T>` | interface | 특정 이벤트에 대한 프록시 인터페이스 |
|
|
52
|
+
|
|
53
|
+
→ See [docs/features.md](./docs/features.md) for details.
|
|
54
|
+
|
|
55
|
+
### File Operations
|
|
56
|
+
|
|
57
|
+
| API | Type | Description |
|
|
58
|
+
|-----|------|-------------|
|
|
59
|
+
| `FileClient` | interface | 파일 업로드(POST)/다운로드(GET) 인터페이스 |
|
|
60
|
+
| `createFileClient()` | function | FileClient 팩토리. hostUrl과 clientName으로 생성 |
|
|
61
|
+
|
|
62
|
+
### ORM Remote Execution
|
|
63
|
+
|
|
64
|
+
| API | Type | Description |
|
|
65
|
+
|-----|------|-------------|
|
|
66
|
+
| `OrmClientConnector` | interface | DbContext 원격 트랜잭션 연결 헬퍼 |
|
|
67
|
+
| `createOrmClientConnector()` | function | OrmClientConnector 팩토리. ServiceClient 사용 |
|
|
68
|
+
| `OrmConnectOptions<T>` | interface | ORM 연결 설정 (DbClass, connOpt, dbContextOpt) |
|
|
69
|
+
| `OrmClientDbContextExecutor` | class | DbContextExecutor 구현체 (서버로 원격 호출) |
|
|
70
|
+
|
|
71
|
+
### Types & Utilities
|
|
72
|
+
|
|
73
|
+
| API | Type | Description |
|
|
74
|
+
|-----|------|-------------|
|
|
75
|
+
| `ServiceConnectionOptions` | interface | 서버 연결 옵션 (host, port, ssl, maxReconnectCount) |
|
|
76
|
+
| `ServiceProgress` | interface | progress 콜백 인터페이스 (request, response, server) |
|
|
77
|
+
| `ServiceProgressState` | interface | progress 상태 (uuid, totalSize, completedSize) |
|
|
78
|
+
| `BlobInput` | type | Blob \| Uint8Array \| ArrayBuffer \| string (Blob 생성자 허용 타입) |
|
|
79
|
+
| `FileCollection` | interface | FileList 호환 컬렉션 인터페이스 |
|
|
80
|
+
| `isWorkerSupported()` | function | Web Worker 지원 여부 확인 |
|
|
81
|
+
|
|
82
|
+
→ See [docs/types.md](./docs/types.md) for details.
|
|
83
|
+
|
|
84
|
+
## Usage Examples
|
|
85
|
+
|
|
86
|
+
### Basic Service Connection & RPC
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { createServiceClient } from "@simplysm/service-client";
|
|
90
|
+
|
|
91
|
+
const client = createServiceClient("my-app", {
|
|
92
|
+
host: "localhost",
|
|
93
|
+
port: 3000,
|
|
94
|
+
ssl: false,
|
|
95
|
+
maxReconnectCount: 10, // 자동 재연결 (0으로 비활성화)
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
await client.connect();
|
|
99
|
+
await client.auth("jwt-token");
|
|
100
|
+
|
|
101
|
+
// 타입 안전한 서비스 프록시
|
|
102
|
+
const userSvc = client.getService<UserService>("User");
|
|
103
|
+
const users = await userSvc.getAll();
|
|
104
|
+
|
|
105
|
+
await client.close();
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Event Subscription
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// 이벤트 리스너 등록 (selector로 필터링)
|
|
112
|
+
const listenerId = await client.addListener(
|
|
113
|
+
"user:created",
|
|
114
|
+
{ userId: 123 }, // info selector (서버와 공유되는 필터 정보)
|
|
115
|
+
async (data) => {
|
|
116
|
+
console.log("새 사용자:", data.name);
|
|
117
|
+
},
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
// 이벤트 발행 (특정 selector에 매칭하는 리스너에만 전송)
|
|
121
|
+
await client.emitEvent(
|
|
122
|
+
"user:created",
|
|
123
|
+
(info) => info.userId === 123,
|
|
124
|
+
{ name: "Alice", email: "alice@example.com" },
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// 리스너 제거
|
|
128
|
+
await client.removeListener(listenerId);
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### File Upload & Download
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// 파일 업로드 (인증 필수)
|
|
135
|
+
const files = [
|
|
136
|
+
new File(["content"], "file.txt", { type: "text/plain" }),
|
|
137
|
+
];
|
|
138
|
+
const results = await client.uploadFile(files);
|
|
139
|
+
console.log("업로드 결과:", results);
|
|
140
|
+
|
|
141
|
+
// 파일 다운로드
|
|
142
|
+
const buffer = await client.downloadFileBuffer("/uploaded/file.txt");
|
|
143
|
+
console.log("다운로드 바이트:", buffer.length);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### ORM Remote Execution
|
|
147
|
+
|
|
148
|
+
```typescript
|
|
149
|
+
import { createOrmClientConnector } from "@simplysm/service-client";
|
|
150
|
+
|
|
151
|
+
const connector = createOrmClientConnector(client);
|
|
152
|
+
|
|
153
|
+
const result = await connector.connect(
|
|
154
|
+
{
|
|
155
|
+
DbClass: MyDbContext,
|
|
156
|
+
connOpt: { configName: "main", username: "user", password: "pass" },
|
|
157
|
+
},
|
|
158
|
+
async (db) => {
|
|
159
|
+
return db.users.select().execute();
|
|
160
|
+
},
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
console.log("조회 결과:", result);
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Progress Tracking
|
|
167
|
+
|
|
168
|
+
```typescript
|
|
169
|
+
const progress = {
|
|
170
|
+
request: (state) => {
|
|
171
|
+
console.log(`요청 전송: ${state.completedSize}/${state.totalSize} bytes`);
|
|
172
|
+
},
|
|
173
|
+
response: (state) => {
|
|
174
|
+
console.log(`응답 수신: ${state.completedSize}/${state.totalSize} bytes`);
|
|
175
|
+
},
|
|
176
|
+
server: (state) => {
|
|
177
|
+
console.log(`서버 처리: ${state.completedSize}/${state.totalSize}`);
|
|
178
|
+
},
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
await client.send("Service", "method", [arg1, arg2], progress);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Connection State & Events
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
// 연결 상태 모니터링
|
|
188
|
+
client.on("state", (state) => {
|
|
189
|
+
console.log("연결 상태:", state); // "connected" | "closed" | "reconnecting"
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
client.on("request-progress", (state) => {
|
|
193
|
+
console.log(`요청 진행: ${state.completedSize}/${state.totalSize}`);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// 연결 확인
|
|
197
|
+
if (client.connected) {
|
|
198
|
+
console.log("서버에 연결됨");
|
|
199
|
+
}
|
|
200
|
+
```
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-protocol-wrapper.d.ts","sourceRoot":"","sources":["../../src/protocol/client-protocol-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,KAAK,EACV,0BAA0B,EAC1B,cAAc,EACd,eAAe,EAChB,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"client-protocol-wrapper.d.ts","sourceRoot":"","sources":["../../src/protocol/client-protocol-wrapper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,KAAK,EACV,0BAA0B,EAC1B,cAAc,EACd,eAAe,EAChB,MAAM,0BAA0B,CAAC;AAclC,MAAM,WAAW,qBAAqB;IACpC,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC;QAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/F,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,0BAA0B,CAAC,cAAc,CAAC,CAAC,CAAC;IAC1E,OAAO,IAAI,IAAI,CAAC;CACjB;AA+ID,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,eAAe,GAAG,qBAAqB,CAyD5F"}
|
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import { LazyGcMap, transfer, Uuid } from "@simplysm/core-common";
|
|
2
|
-
import { isWorkerSupported } from "../types/browser-compat.js";
|
|
3
|
-
// 공유 worker 상태 (싱글턴 패턴)
|
|
4
|
-
let worker;
|
|
2
|
+
import { isBrowserWorkerSupported, isNodeWorkerSupported, isWorkerSupported, } from "../types/browser-compat.js";
|
|
5
3
|
const workerResolvers = new LazyGcMap({
|
|
6
|
-
gcInterval: 5 * 1000,
|
|
7
|
-
expireTime: 60 * 1000,
|
|
4
|
+
gcInterval: 5 * 1000,
|
|
5
|
+
expireTime: 60 * 1000,
|
|
8
6
|
onExpire: (key, item) => {
|
|
9
|
-
// 만료 시 reject (메모리 누수 방지에 필수)
|
|
10
7
|
item.reject(new Error(`Worker 작업 시간 초과 (uuid: ${key})`));
|
|
11
8
|
},
|
|
12
9
|
});
|
|
@@ -17,54 +14,99 @@ function isWorkerAvailable() {
|
|
|
17
14
|
}
|
|
18
15
|
return workerAvailable;
|
|
19
16
|
}
|
|
20
|
-
function
|
|
17
|
+
function setupWorkerHandlers(w) {
|
|
18
|
+
w.onmessage = (event) => {
|
|
19
|
+
const { id, type, result, error } = event.data;
|
|
20
|
+
const resolver = workerResolvers.get(id);
|
|
21
|
+
if (resolver != null) {
|
|
22
|
+
if (type === "success") {
|
|
23
|
+
resolver.resolve(result);
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
const err = new Error(error?.message ?? "알 수 없는 worker 에러");
|
|
27
|
+
err.stack = error?.stack;
|
|
28
|
+
resolver.reject(err);
|
|
29
|
+
}
|
|
30
|
+
workerResolvers.delete(id);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
w.onerror = () => {
|
|
34
|
+
const workerErr = new Error("Worker 초기화 실패");
|
|
35
|
+
for (const resolver of workerResolvers.values()) {
|
|
36
|
+
resolver.reject(workerErr);
|
|
37
|
+
}
|
|
38
|
+
workerResolvers.clear();
|
|
39
|
+
workerInitPromise = undefined;
|
|
40
|
+
workerAvailable = false;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function createNodeWorkerAdapter(nodeWorker) {
|
|
44
|
+
const adapter = {
|
|
45
|
+
onmessage: null,
|
|
46
|
+
onerror: null,
|
|
47
|
+
postMessage(message, transferItems) {
|
|
48
|
+
nodeWorker.postMessage(message, transferItems);
|
|
49
|
+
},
|
|
50
|
+
terminate() {
|
|
51
|
+
void nodeWorker.terminate();
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
nodeWorker.on("message", (data) => {
|
|
55
|
+
adapter.onmessage?.({ data });
|
|
56
|
+
});
|
|
57
|
+
nodeWorker.on("error", (err) => {
|
|
58
|
+
adapter.onerror?.(err);
|
|
59
|
+
});
|
|
60
|
+
return adapter;
|
|
61
|
+
}
|
|
62
|
+
let workerInitPromise;
|
|
63
|
+
async function initWorker() {
|
|
64
|
+
try {
|
|
65
|
+
if (isBrowserWorkerSupported()) {
|
|
66
|
+
// esbuild Worker 번들링 플러그인(sd-worker-bundle)이 이 패턴을 AST에서 인식
|
|
67
|
+
const w = new Worker(new URL("../workers/client-protocol.worker.js", import.meta.url), { type: "module" });
|
|
68
|
+
setupWorkerHandlers(w);
|
|
69
|
+
return w;
|
|
70
|
+
}
|
|
71
|
+
if (isNodeWorkerSupported()) {
|
|
72
|
+
// esbuild Worker 번들링 플러그인이 import.meta.resolve 패턴을 인식
|
|
73
|
+
const workerUrl = import.meta.resolve("../workers/client-protocol.worker.js");
|
|
74
|
+
const workerThreadsId = "worker_threads";
|
|
75
|
+
const { Worker: NodeWorker } = await import(/* @vite-ignore */ workerThreadsId);
|
|
76
|
+
const nodeWorker = new NodeWorker(new URL(workerUrl));
|
|
77
|
+
const adapter = createNodeWorkerAdapter(nodeWorker);
|
|
78
|
+
setupWorkerHandlers(adapter);
|
|
79
|
+
return adapter;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
workerAvailable = false;
|
|
84
|
+
}
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
async function getWorker() {
|
|
21
88
|
if (!isWorkerAvailable()) {
|
|
22
89
|
return undefined;
|
|
23
90
|
}
|
|
24
|
-
if (
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
worker.onmessage = (event) => {
|
|
29
|
-
const { id, type, result, error } = event.data;
|
|
30
|
-
const resolver = workerResolvers.get(id);
|
|
31
|
-
if (resolver != null) {
|
|
32
|
-
if (type === "success") {
|
|
33
|
-
resolver.resolve(result);
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
const err = new Error(error?.message ?? "알 수 없는 worker 에러");
|
|
37
|
-
err.stack = error?.stack;
|
|
38
|
-
resolver.reject(err);
|
|
39
|
-
}
|
|
40
|
-
workerResolvers.delete(id);
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
worker.onerror = () => {
|
|
44
|
-
// Worker 로드 실패 또는 초기화 에러 시
|
|
45
|
-
// 대기 중인 모든 요청 즉시 reject
|
|
46
|
-
const workerErr = new Error("Worker 초기화 실패");
|
|
47
|
-
for (const resolver of workerResolvers.values()) {
|
|
48
|
-
resolver.reject(workerErr);
|
|
91
|
+
if (workerInitPromise == null) {
|
|
92
|
+
workerInitPromise = initWorker().then((w) => {
|
|
93
|
+
if (w == null) {
|
|
94
|
+
workerAvailable = false;
|
|
49
95
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
worker = undefined;
|
|
53
|
-
workerAvailable = false;
|
|
54
|
-
};
|
|
96
|
+
return w;
|
|
97
|
+
});
|
|
55
98
|
}
|
|
56
|
-
return
|
|
99
|
+
return workerInitPromise;
|
|
57
100
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Worker에 작업을 위임하고 결과를 대기
|
|
60
|
-
* 참고: workerAvailable이 true일 때만 호출할 것
|
|
61
|
-
*/
|
|
62
101
|
async function runWorker(type, data, transferables = []) {
|
|
102
|
+
const w = await getWorker();
|
|
103
|
+
if (w == null) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
63
106
|
return new Promise((resolve, reject) => {
|
|
64
107
|
const id = Uuid.generate().toString();
|
|
65
108
|
workerResolvers.set(id, { resolve, reject });
|
|
66
|
-
|
|
67
|
-
getWorker().postMessage({ id, type, data }, transferables);
|
|
109
|
+
w.postMessage({ id, type, data }, transferables);
|
|
68
110
|
});
|
|
69
111
|
}
|
|
70
112
|
export function createClientProtocolWrapper(protocol) {
|
|
@@ -89,10 +131,11 @@ export function createClientProtocolWrapper(protocol) {
|
|
|
89
131
|
if (!isWorkerAvailable() || !shouldUseWorkerForEncode(message)) {
|
|
90
132
|
return protocol.encode(uuid, message);
|
|
91
133
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
134
|
+
const workerResult = await runWorker("encode", { uuid, message });
|
|
135
|
+
if (workerResult == null) {
|
|
136
|
+
return protocol.encode(uuid, message);
|
|
137
|
+
}
|
|
138
|
+
return workerResult;
|
|
96
139
|
}
|
|
97
140
|
async function decode(bytes) {
|
|
98
141
|
const totalSize = bytes.length;
|
|
@@ -100,10 +143,10 @@ export function createClientProtocolWrapper(protocol) {
|
|
|
100
143
|
if (!isWorkerAvailable() || totalSize <= SIZE_THRESHOLD) {
|
|
101
144
|
return protocol.decode(bytes);
|
|
102
145
|
}
|
|
103
|
-
// [Worker]
|
|
104
|
-
// Zero-copy 전송 (버퍼 소유권이 Worker로 이동)
|
|
105
146
|
const rawResult = await runWorker("decode", bytes, [bytes.buffer]);
|
|
106
|
-
|
|
147
|
+
if (rawResult == null) {
|
|
148
|
+
return protocol.decode(bytes);
|
|
149
|
+
}
|
|
107
150
|
return transfer.decode(rawResult);
|
|
108
151
|
}
|
|
109
152
|
return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-protocol-wrapper.js","sourceRoot":"","sources":["../../src/protocol/client-protocol-wrapper.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"client-protocol-wrapper.js","sourceRoot":"","sources":["../../src/protocol/client-protocol-wrapper.ts"],"names":[],"mappings":"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"}
|
|
@@ -15,6 +15,17 @@ export interface FileCollection {
|
|
|
15
15
|
[index: number]: File;
|
|
16
16
|
[Symbol.iterator](): IterableIterator<File>;
|
|
17
17
|
}
|
|
18
|
-
/** Web Worker
|
|
18
|
+
/** Web Worker 최소 인터페이스 (DOM lib 없이도 타입체크 통과용) */
|
|
19
|
+
export interface BrowserWorker {
|
|
20
|
+
onmessage: ((event: MessageEvent) => void) | null;
|
|
21
|
+
onerror: ((event: Event) => void) | null;
|
|
22
|
+
postMessage(message: unknown, transfer?: unknown[]): void;
|
|
23
|
+
terminate(): void;
|
|
24
|
+
}
|
|
25
|
+
/** DOM Worker API 지원 여부 확인 (browser 환경) */
|
|
26
|
+
export declare function isBrowserWorkerSupported(): boolean;
|
|
27
|
+
/** Node.js worker_threads 지원 여부 확인 */
|
|
28
|
+
export declare function isNodeWorkerSupported(): boolean;
|
|
29
|
+
/** Worker 오프로딩 지원 여부 (browser DOM Worker 또는 Node.js worker_threads) */
|
|
19
30
|
export declare function isWorkerSupported(): boolean;
|
|
20
31
|
//# sourceMappingURL=browser-compat.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-compat.d.ts","sourceRoot":"","sources":["../../src/types/browser-compat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,WAAW,GAAG,MAAM,CAAC;AAE9E;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACjC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,
|
|
1
|
+
{"version":3,"file":"browser-compat.d.ts","sourceRoot":"","sources":["../../src/types/browser-compat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,sDAAsD;AACtD,MAAM,MAAM,SAAS,GAAG,IAAI,GAAG,UAAU,CAAC,WAAW,CAAC,GAAG,WAAW,GAAG,MAAM,CAAC;AAE9E;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAAC;IACjC,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,iDAAiD;AACjD,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,YAAY,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IAClD,OAAO,EAAE,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzC,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;IAC1D,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,2CAA2C;AAC3C,wBAAgB,wBAAwB,IAAI,OAAO,CAElD;AAED,sCAAsC;AACtC,wBAAgB,qBAAqB,IAAI,OAAO,CAK/C;AAED,uEAAuE;AACvE,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C"}
|
|
@@ -3,8 +3,17 @@
|
|
|
3
3
|
* DOM-only 타입(FileList, BlobPart)을 대체하여
|
|
4
4
|
* Node.js / browser 양쪽 환경에서 typecheck가 통과하도록 한다.
|
|
5
5
|
*/
|
|
6
|
-
/**
|
|
7
|
-
export function
|
|
6
|
+
/** DOM Worker API 지원 여부 확인 (browser 환경) */
|
|
7
|
+
export function isBrowserWorkerSupported() {
|
|
8
8
|
return "Worker" in globalThis;
|
|
9
9
|
}
|
|
10
|
+
/** Node.js worker_threads 지원 여부 확인 */
|
|
11
|
+
export function isNodeWorkerSupported() {
|
|
12
|
+
const proc = globalThis["process"];
|
|
13
|
+
return proc?.versions?.node != null;
|
|
14
|
+
}
|
|
15
|
+
/** Worker 오프로딩 지원 여부 (browser DOM Worker 또는 Node.js worker_threads) */
|
|
16
|
+
export function isWorkerSupported() {
|
|
17
|
+
return isBrowserWorkerSupported() || isNodeWorkerSupported();
|
|
18
|
+
}
|
|
10
19
|
//# sourceMappingURL=browser-compat.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-compat.js","sourceRoot":"","sources":["../../src/types/browser-compat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"browser-compat.js","sourceRoot":"","sources":["../../src/types/browser-compat.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,2CAA2C;AAC3C,MAAM,UAAU,wBAAwB;IACtC,OAAO,QAAQ,IAAI,UAAU,CAAC;AAChC,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,qBAAqB;IACnC,MAAM,IAAI,GAAI,UAAsC,CAAC,SAAS,CAEjD,CAAC;IACd,OAAO,IAAI,EAAE,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC;AACtC,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,iBAAiB;IAC/B,OAAO,wBAAwB,EAAE,IAAI,qBAAqB,EAAE,CAAC;AAC/D,CAAC"}
|
|
@@ -2,43 +2,54 @@
|
|
|
2
2
|
import { createServiceProtocol } from "@simplysm/service-common";
|
|
3
3
|
import { transfer } from "@simplysm/core-common";
|
|
4
4
|
const protocol = createServiceProtocol();
|
|
5
|
-
|
|
6
|
-
const { id, type, data } = event.data;
|
|
5
|
+
function handleRequest(msg) {
|
|
7
6
|
try {
|
|
8
7
|
let result;
|
|
9
8
|
let transferList = [];
|
|
10
|
-
if (type === "encode") {
|
|
11
|
-
|
|
12
|
-
// message는 이미 Plain Object임 (Structured Clone을 통해 전달)
|
|
13
|
-
const { uuid, message } = data;
|
|
9
|
+
if (msg.type === "encode") {
|
|
10
|
+
const { uuid, message } = msg.data;
|
|
14
11
|
const { chunks } = protocol.encode(uuid, message);
|
|
15
|
-
// Buffer[]는 전송 가능하므로 결과로 반환
|
|
16
12
|
result = chunks;
|
|
17
|
-
// 결과 chunk의 내부 ArrayBuffer를 전송 목록에 추가
|
|
18
13
|
transferList = chunks.map((chunk) => chunk.buffer);
|
|
19
14
|
}
|
|
20
15
|
else {
|
|
21
|
-
|
|
22
|
-
// data는 Uint8Array로 전달됨
|
|
23
|
-
const bytes = new Uint8Array(data);
|
|
16
|
+
const bytes = new Uint8Array(msg.data);
|
|
24
17
|
const decodeResult = protocol.decode(bytes);
|
|
25
|
-
// 결과 객체를 전송 가능한 형태로 변환 (zero-copy 준비)
|
|
26
18
|
const encoded = transfer.encode(decodeResult);
|
|
27
19
|
result = encoded.result;
|
|
28
20
|
transferList = encoded.transferList;
|
|
29
21
|
}
|
|
30
|
-
|
|
31
|
-
self.postMessage({ id, type: "success", result }, transferList);
|
|
22
|
+
return { response: { id: msg.id, type: "success", result }, transferList };
|
|
32
23
|
}
|
|
33
24
|
catch (err) {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
return {
|
|
26
|
+
response: {
|
|
27
|
+
id: msg.id,
|
|
28
|
+
type: "error",
|
|
29
|
+
error: err instanceof Error
|
|
30
|
+
? { message: err.message, stack: err.stack }
|
|
31
|
+
: { message: String(err) },
|
|
32
|
+
},
|
|
33
|
+
transferList: [],
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (typeof self !== "undefined" && typeof self.postMessage === "function") {
|
|
38
|
+
// Browser Worker
|
|
39
|
+
self.onmessage = (event) => {
|
|
40
|
+
const { response, transferList } = handleRequest(event.data);
|
|
41
|
+
self.postMessage(response, transferList);
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
// Node.js worker_threads
|
|
46
|
+
const workerThreadsId = "worker_threads";
|
|
47
|
+
const { parentPort } = await import(workerThreadsId);
|
|
48
|
+
if (parentPort != null) {
|
|
49
|
+
parentPort.on("message", (data) => {
|
|
50
|
+
const { response, transferList } = handleRequest(data);
|
|
51
|
+
parentPort.postMessage(response, transferList);
|
|
41
52
|
});
|
|
42
53
|
}
|
|
43
|
-
}
|
|
54
|
+
}
|
|
44
55
|
//# sourceMappingURL=client-protocol.worker.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client-protocol.worker.js","sourceRoot":"","sources":["../../src/workers/client-protocol.worker.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"client-protocol.worker.js","sourceRoot":"","sources":["../../src/workers/client-protocol.worker.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC,OAAO,EAAE,qBAAqB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,MAAM,QAAQ,GAAG,qBAAqB,EAAE,CAAC;AAQzC,SAAS,aAAa,CAAC,GAAkB;IAIvC,IAAI,CAAC;QACH,IAAI,MAAe,CAAC;QACpB,IAAI,YAAY,GAAmB,EAAE,CAAC;QAEtC,IAAI,GAAG,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC1B,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,GAAG,CAAC,IAG7B,CAAC;YACF,MAAM,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC,MAAM,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YAClD,MAAM,GAAG,MAAM,CAAC;YAChB,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,MAAqB,CAAC,CAAC;QACpE,CAAC;aAAM,CAAC;YACN,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,GAAG,CAAC,IAAmB,CAAC,CAAC;YACtD,MAAM,YAAY,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YAC5C,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC;YAC9C,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;YACxB,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC;QACtC,CAAC;QAED,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,EAAE,GAAG,CAAC,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,YAAY,EAAE,CAAC;IAC7E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,QAAQ,EAAE;gBACR,EAAE,EAAE,GAAG,CAAC,EAAE;gBACV,IAAI,EAAE,OAAO;gBACb,KAAK,EAAE,GAAG,YAAY,KAAK;oBACzB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE;oBAC5C,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE;aAC7B;YACD,YAAY,EAAE,EAAE;SACjB,CAAC;IACJ,CAAC;AACH,CAAC;AAED,IAAI,OAAO,IAAI,KAAK,WAAW,IAAI,OAAO,IAAI,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;IAC1E,iBAAiB;IACjB,IAAI,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;QACvC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,aAAa,CAAC,KAAK,CAAC,IAAqB,CAAC,CAAC;QAC9E,IAAI,CAAC,WAAW,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;IAC3C,CAAC,CAAC;AACJ,CAAC;KAAM,CAAC;IACN,yBAAyB;IACzB,MAAM,eAAe,GAAG,gBAAgB,CAAC;IACzC,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,MAAM,CAAC,eAAe,CAAC,CAAC;IACrD,IAAI,UAAU,IAAI,IAAI,EAAE,CAAC;QACvB,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,IAAa,EAAE,EAAE;YACzC,MAAM,EAAE,QAAQ,EAAE,YAAY,EAAE,GAAG,aAAa,CAAC,IAAqB,CAAC,CAAC;YACxE,UAAU,CAAC,WAAW,CAAC,QAAQ,EAAE,YAA6B,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;IACL,CAAC;AACH,CAAC"}
|