@hile/message-modem 1.0.7 → 2.0.1
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/dist/index.d.ts +25 -6
- package/dist/index.js +233 -118
- package/package.json +2 -2
- package/SKILL.md +0 -180
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Readable } from 'node:stream';
|
|
1
2
|
export * from './exception.js';
|
|
2
3
|
export declare enum MESSAGE_MODEM_TYPE {
|
|
3
4
|
REQUEST = 0,
|
|
@@ -8,6 +9,7 @@ export interface MessageTransferFormat<T = any> {
|
|
|
8
9
|
id: number;
|
|
9
10
|
mode: MESSAGE_MODEM_TYPE;
|
|
10
11
|
twoway: boolean;
|
|
12
|
+
stream?: boolean;
|
|
11
13
|
data?: T;
|
|
12
14
|
}
|
|
13
15
|
export interface MessageReturnFormat<T = any> {
|
|
@@ -15,10 +17,17 @@ export interface MessageReturnFormat<T = any> {
|
|
|
15
17
|
data: T;
|
|
16
18
|
message: string;
|
|
17
19
|
}
|
|
20
|
+
export interface MessageStreamChunk<T = any> {
|
|
21
|
+
status: string | number;
|
|
22
|
+
seq: number;
|
|
23
|
+
payload: T;
|
|
24
|
+
final: boolean;
|
|
25
|
+
}
|
|
18
26
|
export declare abstract class MessageModem {
|
|
19
27
|
private id;
|
|
20
28
|
private readonly aborts;
|
|
21
29
|
private readonly stacks;
|
|
30
|
+
private readonly streams;
|
|
22
31
|
protected _dispose(): void;
|
|
23
32
|
/**
|
|
24
33
|
* 创建自增 ID
|
|
@@ -36,7 +45,7 @@ export declare abstract class MessageModem {
|
|
|
36
45
|
* @param data - 消息数据
|
|
37
46
|
* @returns
|
|
38
47
|
*/
|
|
39
|
-
protected abstract exec(data: any): Promise<any>;
|
|
48
|
+
protected abstract exec(data: any, signal?: AbortSignal): Promise<any>;
|
|
40
49
|
/**
|
|
41
50
|
* 创建发送消息数据
|
|
42
51
|
* @param mode - 消息类型
|
|
@@ -48,19 +57,27 @@ export declare abstract class MessageModem {
|
|
|
48
57
|
* 发送消息
|
|
49
58
|
* @param data - 消息数据
|
|
50
59
|
* @param timeout - 超时时间
|
|
60
|
+
* @param signal - 中止信号
|
|
51
61
|
* @returns 消息响应
|
|
52
62
|
*/
|
|
53
|
-
protected _send<T = any>(data:
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
63
|
+
protected _send<T = any>(data: any, options?: {
|
|
64
|
+
timeout?: number;
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}): Promise<T>;
|
|
57
67
|
/**
|
|
58
68
|
* 推送消息
|
|
59
69
|
* @param data - 消息数据
|
|
60
70
|
* @param timeout - 超时时间
|
|
71
|
+
* @param signal - 中止信号
|
|
61
72
|
* @returns 消息响应
|
|
62
73
|
*/
|
|
63
|
-
protected _push<T = any>(data: T,
|
|
74
|
+
protected _push<T = any>(data: T, options?: {
|
|
75
|
+
timeout?: number;
|
|
76
|
+
signal?: AbortSignal;
|
|
77
|
+
}): void;
|
|
78
|
+
protected _stream(data: any, options?: {
|
|
79
|
+
signal?: AbortSignal;
|
|
80
|
+
}): Readable;
|
|
64
81
|
/**
|
|
65
82
|
* 写入消息
|
|
66
83
|
* @param data - 消息数据
|
|
@@ -78,6 +95,8 @@ export declare abstract class MessageModem {
|
|
|
78
95
|
* @param msg - 消息数据
|
|
79
96
|
*/
|
|
80
97
|
private onResponse;
|
|
98
|
+
private onStreamRequest;
|
|
99
|
+
private onStreamResponse;
|
|
81
100
|
/**
|
|
82
101
|
* 接收消息
|
|
83
102
|
* @param msg - 消息数据
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { AbortException, Exception, TimeoutException } from "./exception.js";
|
|
2
|
+
import { Readable } from 'node:stream';
|
|
2
3
|
export * from './exception.js';
|
|
3
4
|
export var MESSAGE_MODEM_TYPE;
|
|
4
5
|
(function (MESSAGE_MODEM_TYPE) {
|
|
@@ -10,15 +11,20 @@ export class MessageModem {
|
|
|
10
11
|
id = 0;
|
|
11
12
|
aborts = new Map();
|
|
12
13
|
stacks = new Map();
|
|
14
|
+
streams = new Map();
|
|
13
15
|
_dispose() {
|
|
14
16
|
for (const { reject } of this.stacks.values()) {
|
|
15
17
|
reject(new AbortException());
|
|
16
18
|
}
|
|
17
|
-
for (const
|
|
18
|
-
|
|
19
|
+
for (const controller of this.aborts.values()) {
|
|
20
|
+
controller.abort();
|
|
21
|
+
}
|
|
22
|
+
for (const stream of this.streams.values()) {
|
|
23
|
+
stream.destroy(new AbortException());
|
|
19
24
|
}
|
|
20
25
|
this.aborts.clear();
|
|
21
26
|
this.stacks.clear();
|
|
27
|
+
this.streams.clear();
|
|
22
28
|
}
|
|
23
29
|
/**
|
|
24
30
|
* 创建自增 ID
|
|
@@ -38,13 +44,14 @@ export class MessageModem {
|
|
|
38
44
|
* @param data - 消息数据
|
|
39
45
|
* @returns 消息数据
|
|
40
46
|
*/
|
|
41
|
-
createPostData(mode, data, twoway = true) {
|
|
47
|
+
createPostData(mode, data, twoway = true, stream = false) {
|
|
42
48
|
const id = this.createIncrementId();
|
|
43
49
|
const state = {
|
|
44
|
-
id, twoway, data, mode,
|
|
50
|
+
id, twoway, data, mode, stream,
|
|
45
51
|
};
|
|
46
52
|
if (mode === MESSAGE_MODEM_TYPE.ABORT) {
|
|
47
53
|
state.twoway = false;
|
|
54
|
+
state.stream = false;
|
|
48
55
|
}
|
|
49
56
|
return state;
|
|
50
57
|
}
|
|
@@ -52,19 +59,50 @@ export class MessageModem {
|
|
|
52
59
|
* 发送消息
|
|
53
60
|
* @param data - 消息数据
|
|
54
61
|
* @param timeout - 超时时间
|
|
62
|
+
* @param signal - 中止信号
|
|
55
63
|
* @returns 消息响应
|
|
56
64
|
*/
|
|
57
|
-
_send(data,
|
|
58
|
-
return this._write(data,
|
|
65
|
+
_send(data, options) {
|
|
66
|
+
return this._write(data, {
|
|
67
|
+
timeout: options?.timeout ?? 30000,
|
|
68
|
+
twoway: true,
|
|
69
|
+
signal: options?.signal,
|
|
70
|
+
});
|
|
59
71
|
}
|
|
60
72
|
/**
|
|
61
73
|
* 推送消息
|
|
62
74
|
* @param data - 消息数据
|
|
63
75
|
* @param timeout - 超时时间
|
|
76
|
+
* @param signal - 中止信号
|
|
64
77
|
* @returns 消息响应
|
|
65
78
|
*/
|
|
66
|
-
_push(data,
|
|
67
|
-
this._write(data,
|
|
79
|
+
_push(data, options) {
|
|
80
|
+
this._write(data, {
|
|
81
|
+
timeout: options?.timeout ?? 30000,
|
|
82
|
+
twoway: false,
|
|
83
|
+
signal: options?.signal,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
_stream(data, options) {
|
|
87
|
+
const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, data, true, true);
|
|
88
|
+
const stream = new Readable({ objectMode: true, read() { } });
|
|
89
|
+
this.streams.set(state.id, stream);
|
|
90
|
+
this.post(state);
|
|
91
|
+
const onAbort = () => {
|
|
92
|
+
this.post(this.createPostData(MESSAGE_MODEM_TYPE.ABORT, state.id));
|
|
93
|
+
stream.destroy(new AbortException());
|
|
94
|
+
this.streams.delete(state.id);
|
|
95
|
+
};
|
|
96
|
+
if (options?.signal) {
|
|
97
|
+
options.signal.addEventListener('abort', onAbort);
|
|
98
|
+
}
|
|
99
|
+
stream.on('close', () => {
|
|
100
|
+
if (this.streams.has(state.id)) {
|
|
101
|
+
this.streams.delete(state.id);
|
|
102
|
+
}
|
|
103
|
+
options?.signal?.removeEventListener('abort', onAbort);
|
|
104
|
+
});
|
|
105
|
+
return stream;
|
|
68
106
|
}
|
|
69
107
|
/**
|
|
70
108
|
* 写入消息
|
|
@@ -72,7 +110,10 @@ export class MessageModem {
|
|
|
72
110
|
* @param timeout - 超时时间
|
|
73
111
|
* @returns 消息响应
|
|
74
112
|
*/
|
|
75
|
-
_write(data,
|
|
113
|
+
_write(data, options) {
|
|
114
|
+
const timeout = options?.timeout ?? 30000;
|
|
115
|
+
const twoway = !!options?.twoway;
|
|
116
|
+
const signal = options?.signal;
|
|
76
117
|
// 创建请求消息数据
|
|
77
118
|
const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, data, twoway);
|
|
78
119
|
// 发送消息
|
|
@@ -80,135 +121,101 @@ export class MessageModem {
|
|
|
80
121
|
// 如果消息是单向的,则直接返回
|
|
81
122
|
if (!twoway)
|
|
82
123
|
return;
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
124
|
+
return new Promise((resolve, reject) => {
|
|
125
|
+
const clear = () => {
|
|
126
|
+
if (this.stacks.has(state.id)) {
|
|
127
|
+
this.stacks.delete(state.id);
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
const clean = () => {
|
|
131
|
+
clearTimeout(timer);
|
|
132
|
+
signal?.removeEventListener('abort', onAbort);
|
|
133
|
+
clear();
|
|
134
|
+
};
|
|
135
|
+
const onAbort = () => {
|
|
136
|
+
clearTimeout(timer);
|
|
137
|
+
try {
|
|
138
|
+
this.post(this.createPostData(MESSAGE_MODEM_TYPE.ABORT, state.id));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
/* 例如 WebSocket 已关闭时 send 可能抛错 */
|
|
142
|
+
}
|
|
143
|
+
finally {
|
|
144
|
+
signal?.removeEventListener('abort', onAbort);
|
|
98
145
|
clear();
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
};
|
|
120
|
-
// 失败处理
|
|
121
|
-
const _reject = (e) => {
|
|
122
|
-
clean();
|
|
123
|
-
reject(e);
|
|
124
|
-
};
|
|
125
|
-
// 超时处理
|
|
126
|
-
const timer = setTimeout(() => {
|
|
127
|
-
if (!controller.signal.aborted) {
|
|
128
|
-
controller.abort();
|
|
129
|
-
}
|
|
130
|
-
else {
|
|
131
|
-
_reject(new TimeoutException());
|
|
132
|
-
}
|
|
133
|
-
}, timeout).unref();
|
|
134
|
-
// 添加 Abort 处理函数
|
|
135
|
-
controller.signal.addEventListener('abort', aborthandler);
|
|
136
|
-
// 添加栈
|
|
137
|
-
this.stacks.set(state.id, {
|
|
138
|
-
resolve: _resolve,
|
|
139
|
-
reject: _reject,
|
|
140
|
-
});
|
|
141
|
-
})
|
|
142
|
-
};
|
|
146
|
+
reject(new AbortException());
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
// 成功处理
|
|
150
|
+
const _resolve = (data) => {
|
|
151
|
+
clean();
|
|
152
|
+
resolve(data);
|
|
153
|
+
};
|
|
154
|
+
// 失败处理
|
|
155
|
+
const _reject = (e) => {
|
|
156
|
+
clean();
|
|
157
|
+
reject(e);
|
|
158
|
+
};
|
|
159
|
+
const timer = setTimeout(() => _reject(new TimeoutException()), timeout).unref();
|
|
160
|
+
signal?.addEventListener('abort', onAbort);
|
|
161
|
+
this.stacks.set(state.id, {
|
|
162
|
+
resolve: _resolve,
|
|
163
|
+
reject: _reject,
|
|
164
|
+
});
|
|
165
|
+
});
|
|
143
166
|
}
|
|
144
167
|
/**
|
|
145
168
|
* 处理请求消息
|
|
146
169
|
* @param msg - 消息数据
|
|
147
170
|
*/
|
|
148
171
|
onRequest(msg) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
this.
|
|
153
|
-
|
|
154
|
-
]).then(value => {
|
|
155
|
-
// 如果消息执行失败
|
|
156
|
-
if (value?.e) {
|
|
157
|
-
// 如果消息是双向的,则发送响应消息
|
|
158
|
-
if (msg.twoway) {
|
|
159
|
-
this.post({
|
|
160
|
-
id: msg.id,
|
|
161
|
-
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
162
|
-
twoway: false,
|
|
163
|
-
data: {
|
|
164
|
-
status: value.e instanceof Exception ? value.e.status : 500,
|
|
165
|
-
data: null,
|
|
166
|
-
message: value.e.message,
|
|
167
|
-
}
|
|
168
|
-
});
|
|
169
|
-
}
|
|
172
|
+
const controller = new AbortController();
|
|
173
|
+
this.aborts.set(msg.id, controller);
|
|
174
|
+
controller.signal.addEventListener('abort', () => {
|
|
175
|
+
if (this.aborts.has(msg.id)) {
|
|
176
|
+
this.aborts.delete(msg.id);
|
|
170
177
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
178
|
+
});
|
|
179
|
+
this.exec(msg.data, controller.signal)
|
|
180
|
+
.then(value => {
|
|
181
|
+
if (controller.signal.aborted)
|
|
182
|
+
return;
|
|
183
|
+
if (isAsyncIterable(value)) {
|
|
184
|
+
throw new Exception(500, 'Async iterable is not supported');
|
|
185
|
+
}
|
|
186
|
+
if (msg.twoway) {
|
|
187
|
+
this.post({
|
|
188
|
+
id: msg.id,
|
|
189
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
190
|
+
twoway: false,
|
|
191
|
+
data: {
|
|
192
|
+
status: 200,
|
|
193
|
+
data: value,
|
|
194
|
+
}
|
|
195
|
+
});
|
|
184
196
|
}
|
|
185
|
-
})
|
|
186
|
-
|
|
197
|
+
})
|
|
198
|
+
.catch(e => {
|
|
199
|
+
if (controller.signal.aborted)
|
|
187
200
|
return;
|
|
188
|
-
// 如果消息是双向的,则发送响应消息
|
|
189
201
|
if (msg.twoway) {
|
|
190
|
-
// 发送响应消息
|
|
191
|
-
const code = e instanceof Exception ? e.status : 500;
|
|
192
202
|
this.post({
|
|
193
203
|
id: msg.id,
|
|
194
204
|
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
195
205
|
twoway: false,
|
|
196
206
|
data: {
|
|
197
|
-
status:
|
|
207
|
+
status: e instanceof Exception ? e.status : 500,
|
|
198
208
|
data: null,
|
|
199
209
|
message: e.message,
|
|
200
210
|
}
|
|
201
211
|
});
|
|
202
212
|
}
|
|
203
|
-
})
|
|
213
|
+
})
|
|
214
|
+
.finally(() => {
|
|
204
215
|
// 删除 Abort 处理函数
|
|
205
216
|
if (this.aborts.has(msg.id)) {
|
|
206
217
|
this.aborts.delete(msg.id);
|
|
207
218
|
}
|
|
208
|
-
// 清理栈
|
|
209
|
-
if (this.stacks.has(msg.id)) {
|
|
210
|
-
this.stacks.delete(msg.id);
|
|
211
|
-
}
|
|
212
219
|
});
|
|
213
220
|
}
|
|
214
221
|
/**
|
|
@@ -230,6 +237,99 @@ export class MessageModem {
|
|
|
230
237
|
}
|
|
231
238
|
}
|
|
232
239
|
}
|
|
240
|
+
onStreamRequest(msg) {
|
|
241
|
+
const controller = new AbortController();
|
|
242
|
+
this.aborts.set(msg.id, controller);
|
|
243
|
+
controller.signal.addEventListener('abort', () => {
|
|
244
|
+
if (this.aborts.has(msg.id)) {
|
|
245
|
+
this.aborts.delete(msg.id);
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
this.exec(msg.data, controller.signal)
|
|
249
|
+
.then(async (value) => {
|
|
250
|
+
if (!isAsyncIterable(value)) {
|
|
251
|
+
throw new Exception(500, 'Invalid async iterable');
|
|
252
|
+
}
|
|
253
|
+
let i = 0;
|
|
254
|
+
for await (const chunk of value) {
|
|
255
|
+
if (controller.signal.aborted)
|
|
256
|
+
return;
|
|
257
|
+
this.post({
|
|
258
|
+
id: msg.id,
|
|
259
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
260
|
+
stream: true,
|
|
261
|
+
data: {
|
|
262
|
+
status: 200,
|
|
263
|
+
seq: i++,
|
|
264
|
+
payload: chunk,
|
|
265
|
+
final: false,
|
|
266
|
+
},
|
|
267
|
+
twoway: false,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
if (controller.signal.aborted)
|
|
271
|
+
return;
|
|
272
|
+
this.post({
|
|
273
|
+
id: msg.id,
|
|
274
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
275
|
+
stream: true,
|
|
276
|
+
data: {
|
|
277
|
+
status: 200,
|
|
278
|
+
seq: i++,
|
|
279
|
+
payload: undefined,
|
|
280
|
+
final: true,
|
|
281
|
+
},
|
|
282
|
+
twoway: false,
|
|
283
|
+
});
|
|
284
|
+
})
|
|
285
|
+
.catch(e => {
|
|
286
|
+
if (controller.signal.aborted)
|
|
287
|
+
return;
|
|
288
|
+
this.post({
|
|
289
|
+
id: msg.id,
|
|
290
|
+
mode: MESSAGE_MODEM_TYPE.RESPONSE,
|
|
291
|
+
stream: true,
|
|
292
|
+
data: {
|
|
293
|
+
status: e instanceof Exception ? e.status : 500,
|
|
294
|
+
seq: 0,
|
|
295
|
+
payload: e instanceof Exception ? e.message : 'Unknown error',
|
|
296
|
+
final: true,
|
|
297
|
+
},
|
|
298
|
+
twoway: false,
|
|
299
|
+
});
|
|
300
|
+
})
|
|
301
|
+
.finally(() => {
|
|
302
|
+
if (this.aborts.has(msg.id)) {
|
|
303
|
+
this.aborts.delete(msg.id);
|
|
304
|
+
}
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
onStreamResponse(msg) {
|
|
308
|
+
const id = msg.id;
|
|
309
|
+
const res = msg.data;
|
|
310
|
+
// 如果栈中存在该消息,则处理响应消息
|
|
311
|
+
if (this.streams.has(id)) {
|
|
312
|
+
const stream = this.streams.get(id);
|
|
313
|
+
if (res) {
|
|
314
|
+
if (res.status === 200) {
|
|
315
|
+
if (res.final) {
|
|
316
|
+
stream.push(null);
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
stream.push(res.payload);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
const err = new Exception(res.status, res.payload);
|
|
324
|
+
setImmediate(() => stream.destroy(err));
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
const err = new Exception(404, 'Empty chunk data');
|
|
329
|
+
setImmediate(() => stream.destroy(err));
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
233
333
|
/**
|
|
234
334
|
* 接收消息
|
|
235
335
|
* @param msg - 消息数据
|
|
@@ -239,20 +339,35 @@ export class MessageModem {
|
|
|
239
339
|
switch (msg.mode) {
|
|
240
340
|
// 处理请求消息
|
|
241
341
|
case MESSAGE_MODEM_TYPE.REQUEST:
|
|
242
|
-
|
|
342
|
+
if (msg.stream) {
|
|
343
|
+
this.onStreamRequest(msg);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
this.onRequest(msg);
|
|
347
|
+
}
|
|
243
348
|
break;
|
|
244
349
|
// 处理响应消息
|
|
245
350
|
case MESSAGE_MODEM_TYPE.RESPONSE:
|
|
246
|
-
|
|
351
|
+
if (msg.stream) {
|
|
352
|
+
this.onStreamResponse(msg);
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
this.onResponse(msg);
|
|
356
|
+
}
|
|
247
357
|
break;
|
|
248
358
|
// 处理终止消息
|
|
249
359
|
case MESSAGE_MODEM_TYPE.ABORT:
|
|
250
360
|
const id = msg.data;
|
|
251
361
|
if (this.aborts.has(id)) {
|
|
252
|
-
const
|
|
253
|
-
|
|
254
|
-
|
|
362
|
+
const controller = this.aborts.get(id);
|
|
363
|
+
if (!controller.signal.aborted) {
|
|
364
|
+
controller.abort();
|
|
365
|
+
}
|
|
255
366
|
}
|
|
367
|
+
break;
|
|
256
368
|
}
|
|
257
369
|
}
|
|
258
370
|
}
|
|
371
|
+
function isAsyncIterable(value) {
|
|
372
|
+
return value != null && typeof value[Symbol.asyncIterator] === 'function';
|
|
373
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hile/message-modem",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -21,5 +21,5 @@
|
|
|
21
21
|
"fix-esm-import-path": "^1.10.3",
|
|
22
22
|
"vitest": "^4.0.18"
|
|
23
23
|
},
|
|
24
|
-
"gitHead": "
|
|
24
|
+
"gitHead": "8e0fd1f78b5a8abd21218d1f596ada2533a0c8e7"
|
|
25
25
|
}
|
package/SKILL.md
DELETED
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: message-modem
|
|
3
|
-
description: Code generation and contribution rules for @hile/message-modem. Use when editing this package or when the user asks about @hile/message-modem patterns or API.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# @hile/message-modem
|
|
7
|
-
|
|
8
|
-
本文档是面向 AI 编码模型和人类开发者的 **代码生成规范**,阅读后应能正确地使用本库编写符合架构规则的代码。
|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## 1. 架构总览
|
|
13
|
-
|
|
14
|
-
`@hile/message-modem` 是一个 **传输无关的请求/响应消息通信抽象层**。它将底层传输(WebSocket、postMessage、IPC 等)与业务逻辑解耦,提供统一的 _send/receive 语义。
|
|
15
|
-
|
|
16
|
-
核心职责:
|
|
17
|
-
|
|
18
|
-
- 自增 ID 管理与安全重置(超过 `MAX_SAFE_INTEGER` 时归零)
|
|
19
|
-
- 请求/响应配对(通过 ID + stacks Map)
|
|
20
|
-
- 超时控制(基于 `AbortController` + `setTimeout`)
|
|
21
|
-
- 主动中止(abort):发送方可中止等待,接收方可取消正在执行的任务
|
|
22
|
-
- 错误传播:`Exception` 携带 `status`;非 `Exception` 错误映射为 500
|
|
23
|
-
|
|
24
|
-
导出方式:
|
|
25
|
-
|
|
26
|
-
主入口 `index.ts` 通过 `export * from './exception'` 重新导出所有异常类,因此使用者可以从 `@hile/message-modem` 统一导入所有类型:
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
import { MessageModem, Exception, AbortException, TimeoutException, MESSAGE_MODEM_TYPE } from '@hile/message-modem';
|
|
30
|
-
```
|
|
31
|
-
|
|
32
|
-
继承关系:
|
|
33
|
-
|
|
34
|
-
```
|
|
35
|
-
MessageModem (abstract)
|
|
36
|
-
├── post(data) — 子类实现:如何发送到远端
|
|
37
|
-
└── exec(data) — 子类实现:如何处理收到的请求
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
---
|
|
41
|
-
|
|
42
|
-
## 2. 类型签名
|
|
43
|
-
|
|
44
|
-
```typescript
|
|
45
|
-
// ---- 枚举 ----
|
|
46
|
-
enum MESSAGE_MODEM_TYPE {
|
|
47
|
-
REQUEST, // 请求
|
|
48
|
-
RESPONSE, // 响应
|
|
49
|
-
ABORT, // 中止
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// ---- 传输格式 ----
|
|
53
|
-
interface MessageTransferFormat<T = any> {
|
|
54
|
-
id: number;
|
|
55
|
-
mode: MESSAGE_MODEM_TYPE;
|
|
56
|
-
twoway: boolean;
|
|
57
|
-
data?: T;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ---- 响应格式 ----
|
|
61
|
-
interface MessageReturnFormat<T = any> {
|
|
62
|
-
status: string | number;
|
|
63
|
-
data: T;
|
|
64
|
-
message: string;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// ---- 异常 ----
|
|
68
|
-
class Exception extends Error {
|
|
69
|
-
readonly status: number | string;
|
|
70
|
-
constructor(status: number | string, msg: string);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
class TimeoutException extends Exception {
|
|
74
|
-
static readonly code = 'ETIMEDOUT';
|
|
75
|
-
constructor(msg?: string); // 默认 'Timeout'
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
class AbortException extends Exception {
|
|
79
|
-
static readonly code = 'ECONNABORTED';
|
|
80
|
-
constructor(msg?: string); // 默认 'Abort'
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---- 抽象基类 ----
|
|
84
|
-
abstract class MessageModem {
|
|
85
|
-
protected abstract post<T>(data: MessageTransferFormat<T>): void;
|
|
86
|
-
protected abstract exec(data: any): Promise<any>;
|
|
87
|
-
protected _send<T>(data: T, timeout?: number): {
|
|
88
|
-
abort: () => void;
|
|
89
|
-
response: <U = any>() => Promise<U>;
|
|
90
|
-
};
|
|
91
|
-
protected _push<T>(data: T, timeout?: number): void;
|
|
92
|
-
public receive(msg: MessageTransferFormat): void;
|
|
93
|
-
}
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
---
|
|
97
|
-
|
|
98
|
-
## 3. 代码生成模板与规则
|
|
99
|
-
|
|
100
|
-
### 3.1 子类实现模板
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
import { MessageModem, type MessageTransferFormat } from '@hile/message-modem';
|
|
104
|
-
|
|
105
|
-
class WebSocketModem extends MessageModem {
|
|
106
|
-
constructor(private ws: WebSocket) {
|
|
107
|
-
super();
|
|
108
|
-
ws.addEventListener('message', (e) => {
|
|
109
|
-
this.receive(JSON.parse(e.data));
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
protected post<T>(data: MessageTransferFormat<T>): void {
|
|
114
|
-
this.ws.send(JSON.stringify(data));
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
protected async exec(data: any): Promise<any> {
|
|
118
|
-
// 处理远端请求的业务逻辑
|
|
119
|
-
return handleRequest(data);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 暴露 _send 为 public
|
|
123
|
-
public request<T>(data: T, timeout?: number) {
|
|
124
|
-
return this._send(data, timeout);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
### 3.2 postMessage 场景模板
|
|
130
|
-
|
|
131
|
-
```typescript
|
|
132
|
-
class IframeModem extends MessageModem {
|
|
133
|
-
constructor(private target: Window, private origin: string) {
|
|
134
|
-
super();
|
|
135
|
-
window.addEventListener('message', (e) => {
|
|
136
|
-
if (e.origin === origin) this.receive(e.data);
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
protected post<T>(data: MessageTransferFormat<T>): void {
|
|
141
|
-
this.target.postMessage(data, this.origin);
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
protected async exec(data: any): Promise<any> {
|
|
145
|
-
return handleIframeRequest(data);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
### 3.3 强制规则
|
|
151
|
-
|
|
152
|
-
| 规则 | 说明 |
|
|
153
|
-
|------|------|
|
|
154
|
-
| **必须实现 `post` 和 `exec`** | 两个 abstract 方法缺一不可 |
|
|
155
|
-
| **`_send` 是 `protected`** | 子类应自行决定暴露方式和命名 |
|
|
156
|
-
| **`_push` 是 `protected`** | 单向推送(`twoway: false`),接收方不回复 RESPONSE |
|
|
157
|
-
| **`receive` 是 `public`** | 必须由外部消息源(事件监听器)调用 |
|
|
158
|
-
| **传输格式必须保持原样** | `post` 发送的对象结构不可修改,对端的 `receive` 依赖完整的 `MessageTransferFormat` |
|
|
159
|
-
| **`exec` 抛出 `Exception` 时 status 会透传** | 其他 Error 一律映射为 500 |
|
|
160
|
-
| **timeout 默认 30s** | 可通过 `_send(data, ms)` 覆盖 |
|
|
161
|
-
| **abort 后 promise reject `AbortException`** | 不要 catch 后吞掉,保持语义清晰 |
|
|
162
|
-
|
|
163
|
-
### 3.4 反模式
|
|
164
|
-
|
|
165
|
-
```typescript
|
|
166
|
-
// ❌ 不要在 post 中做异步操作
|
|
167
|
-
protected async post(data) { await fetch(...); }
|
|
168
|
-
// ✅ post 应该是同步的,异步传输应缓冲
|
|
169
|
-
|
|
170
|
-
// ❌ 不要直接修改 MessageTransferFormat 结构
|
|
171
|
-
this.post({ ...data, extra: 'field' });
|
|
172
|
-
// ✅ 业务数据放在 data 字段内
|
|
173
|
-
|
|
174
|
-
// ❌ 不要忘记连接 receive
|
|
175
|
-
// ✅ 在构造函数中绑定消息事件 → this.receive(parsed)
|
|
176
|
-
|
|
177
|
-
// ❌ 不要在 exec 中吞掉错误
|
|
178
|
-
protected async exec(data) { try { ... } catch { return null; } }
|
|
179
|
-
// ✅ 让错误冒泡,框架会处理错误响应
|
|
180
|
-
```
|