@hile/message-modem 1.0.0 → 1.0.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @hile/message-modem
2
2
 
3
- 传输无关的请求/响应消息通信抽象层。将底层传输机制(WebSocket、postMessage、IPC 等)与业务逻辑解耦,提供统一的 send/receive 语义。
3
+ 传输无关的请求/响应消息通信抽象层。将底层传输机制(WebSocket、postMessage、IPC 等)与业务逻辑解耦,提供统一的 _send/receive 语义。
4
4
 
5
5
  ## 安装
6
6
 
@@ -11,6 +11,8 @@ pnpm add @hile/message-modem
11
11
  ## 核心特性
12
12
 
13
13
  - **传输无关** — 子类只需实现 `post`(如何发送)和 `exec`(如何处理),即可运行于任何通信通道
14
+ - **双向请求/响应** — `_send` 发送请求并等待对端响应(`twoway: true`)
15
+ - **单向推送** — `_push` 发送消息无需对端响应(`twoway: false`)
14
16
  - **请求/响应配对** — 自增 ID + Promise 栈,自动配对请求与响应
15
17
  - **超时控制** — 默认 30 秒,可按请求自定义
16
18
  - **主动中止** — 发送方可 abort 等待,接收方可取消正在执行的任务
@@ -42,7 +44,7 @@ class WebSocketModem extends MessageModem {
42
44
 
43
45
  // 暴露发送方法
44
46
  public request<T>(data: T, timeout?: number) {
45
- return this.send(data, timeout);
47
+ return this._send(data, timeout);
46
48
  }
47
49
  }
48
50
  ```
@@ -62,15 +64,21 @@ console.log(user);
62
64
  abort();
63
65
  ```
64
66
 
65
- ### 第三步:处理超时
67
+ ### 第三步:处理超时与异常
68
+
69
+ 所有异常类均从主入口导出,无需单独引入:
66
70
 
67
71
  ```typescript
72
+ import { AbortException, Exception } from '@hile/message-modem';
73
+
68
74
  try {
69
75
  // 5 秒超时
70
76
  const result = await modem.request(data, 5000).response();
71
77
  } catch (e) {
72
78
  if (e instanceof AbortException) {
73
79
  console.log('请求超时或被中止');
80
+ } else if (e instanceof Exception) {
81
+ console.log(`远端错误 [${e.status}]: ${e.message}`);
74
82
  }
75
83
  }
76
84
  ```
@@ -83,10 +91,11 @@ try {
83
91
  |------|--------|------|
84
92
  | `post(data)` | `protected abstract` | 子类实现:如何将消息发送到远端 |
85
93
  | `exec(data)` | `protected abstract` | 子类实现:如何处理收到的请求,返回 Promise |
86
- | `send(data, timeout?)` | `protected` | 发送请求,返回 `{ abort, response }` |
94
+ | `_send(data, timeout?)` | `protected` | 发送双向请求(`twoway: true`),返回 `{ abort, response }` |
95
+ | `_push(data, timeout?)` | `protected` | 发送单向推送(`twoway: false`),接收方不回复 RESPONSE |
87
96
  | `receive(msg)` | `public` | 接收消息入口,根据 mode 分发处理 |
88
97
 
89
- ### `send` 返回值
98
+ ### `_send` 返回值
90
99
 
91
100
  | 属性 | 类型 | 说明 |
92
101
  |------|------|------|
@@ -130,11 +139,13 @@ interface MessageReturnFormat<T = any> {
130
139
 
131
140
  ## 消息流转
132
141
 
142
+ ### 双向模式(`_send`)
143
+
133
144
  ```
134
145
  发送方 接收方
135
146
  │ │
136
- send(data)
137
- │──── REQUEST ───────────────►│
147
+ _send(data)
148
+ │──── REQUEST (twoway) ──────►│
138
149
  │ │ exec(data)
139
150
  │ │
140
151
  │◄──── RESPONSE ──────────────│
@@ -145,6 +156,17 @@ interface MessageReturnFormat<T = any> {
145
156
  │ │ 取消 exec
146
157
  ```
147
158
 
159
+ ### 单向模式(`_push`)
160
+
161
+ ```
162
+ 发送方 接收方
163
+ │ │
164
+ │ _push(data) │
165
+ │──── REQUEST (!twoway) ─────►│
166
+ │ │ exec(data)
167
+ │ │ (不回复 RESPONSE)
168
+ ```
169
+
148
170
  ## 适用场景
149
171
 
150
172
  - **iframe 通信** — 父子页面 postMessage
package/SKILL.md CHANGED
@@ -11,7 +11,7 @@ description: Code generation and contribution rules for @hile/message-modem. Use
11
11
 
12
12
  ## 1. 架构总览
13
13
 
14
- `@hile/message-modem` 是一个 **传输无关的请求/响应消息通信抽象层**。它将底层传输(WebSocket、postMessage、IPC 等)与业务逻辑解耦,提供统一的 send/receive 语义。
14
+ `@hile/message-modem` 是一个 **传输无关的请求/响应消息通信抽象层**。它将底层传输(WebSocket、postMessage、IPC 等)与业务逻辑解耦,提供统一的 _send/receive 语义。
15
15
 
16
16
  核心职责:
17
17
 
@@ -21,6 +21,14 @@ description: Code generation and contribution rules for @hile/message-modem. Use
21
21
  - 主动中止(abort):发送方可中止等待,接收方可取消正在执行的任务
22
22
  - 错误传播:`Exception` 携带 `status`;非 `Exception` 错误映射为 500
23
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
+
24
32
  继承关系:
25
33
 
26
34
  ```
@@ -76,7 +84,11 @@ class AbortException extends Exception {
76
84
  abstract class MessageModem {
77
85
  protected abstract post<T>(data: MessageTransferFormat<T>): void;
78
86
  protected abstract exec(data: any): Promise<any>;
79
- protected send<T>(data: T, timeout?: number): {
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): {
80
92
  abort: () => void;
81
93
  response: <U = any>() => Promise<U>;
82
94
  };
@@ -110,9 +122,9 @@ class WebSocketModem extends MessageModem {
110
122
  return handleRequest(data);
111
123
  }
112
124
 
113
- // 暴露 send 为 public
114
- public request<T, U>(data: T, timeout?: number) {
115
- return this.send(data, timeout);
125
+ // 暴露 _send 为 public
126
+ public request<T>(data: T, timeout?: number) {
127
+ return this._send(data, timeout);
116
128
  }
117
129
  }
118
130
  ```
@@ -143,11 +155,12 @@ class IframeModem extends MessageModem {
143
155
  | 规则 | 说明 |
144
156
  |------|------|
145
157
  | **必须实现 `post` 和 `exec`** | 两个 abstract 方法缺一不可 |
146
- | **`send` 是 `protected`** | 子类应自行决定暴露方式和命名 |
158
+ | **`_send` 是 `protected`** | 子类应自行决定暴露方式和命名 |
159
+ | **`_push` 是 `protected`** | 单向推送(`twoway: false`),接收方不回复 RESPONSE |
147
160
  | **`receive` 是 `public`** | 必须由外部消息源(事件监听器)调用 |
148
161
  | **传输格式必须保持原样** | `post` 发送的对象结构不可修改,对端的 `receive` 依赖完整的 `MessageTransferFormat` |
149
162
  | **`exec` 抛出 `Exception` 时 status 会透传** | 其他 Error 一律映射为 500 |
150
- | **timeout 默认 30s** | 可通过 `send(data, ms)` 覆盖 |
163
+ | **timeout 默认 30s** | 可通过 `_send(data, ms)` 覆盖 |
151
164
  | **abort 后 promise reject `AbortException`** | 不要 catch 后吞掉,保持语义清晰 |
152
165
 
153
166
  ### 3.4 反模式
@@ -0,0 +1,12 @@
1
+ export declare class Exception extends Error {
2
+ readonly status: number | string;
3
+ constructor(status: number | string, msg: string);
4
+ }
5
+ export declare class TimeoutException extends Exception {
6
+ static readonly code = "ETIMEDOUT";
7
+ constructor(msg?: string);
8
+ }
9
+ export declare class AbortException extends Exception {
10
+ static readonly code = "ECONNABORTED";
11
+ constructor(msg?: string);
12
+ }
@@ -0,0 +1,19 @@
1
+ export class Exception extends Error {
2
+ status;
3
+ constructor(status, msg) {
4
+ super(msg);
5
+ this.status = status;
6
+ }
7
+ }
8
+ export class TimeoutException extends Exception {
9
+ static code = 'ETIMEDOUT';
10
+ constructor(msg = 'Timeout') {
11
+ super(TimeoutException.code, msg);
12
+ }
13
+ }
14
+ export class AbortException extends Exception {
15
+ static code = 'ECONNABORTED';
16
+ constructor(msg = 'Abort') {
17
+ super(AbortException.code, msg);
18
+ }
19
+ }
@@ -0,0 +1,88 @@
1
+ export * from './exception.js';
2
+ export declare enum MESSAGE_MODEM_TYPE {
3
+ REQUEST = 0,
4
+ RESPONSE = 1,
5
+ ABORT = 2
6
+ }
7
+ export interface MessageTransferFormat<T = any> {
8
+ id: number;
9
+ mode: MESSAGE_MODEM_TYPE;
10
+ twoway: boolean;
11
+ data?: T;
12
+ }
13
+ export interface MessageReturnFormat<T = any> {
14
+ status: string | number;
15
+ data: T;
16
+ message: string;
17
+ }
18
+ export declare abstract class MessageModem {
19
+ private id;
20
+ private readonly aborts;
21
+ private readonly stacks;
22
+ /**
23
+ * 创建自增 ID
24
+ * 超过最大安全整数时重置为 0
25
+ * @returns
26
+ */
27
+ private createIncrementId;
28
+ /**
29
+ * 如何发送消息到远端
30
+ * @param data - 消息数据
31
+ */
32
+ protected abstract post<T = any>(data: MessageTransferFormat<T>): void;
33
+ /**
34
+ * 如何执行消息
35
+ * @param data - 消息数据
36
+ * @returns
37
+ */
38
+ protected abstract exec(data: any): Promise<any>;
39
+ /**
40
+ * 创建发送消息数据
41
+ * @param mode - 消息类型
42
+ * @param data - 消息数据
43
+ * @returns 消息数据
44
+ */
45
+ private createPostData;
46
+ /**
47
+ * 发送消息
48
+ * @param data - 消息数据
49
+ * @param timeout - 超时时间
50
+ * @returns 消息响应
51
+ */
52
+ protected _send<T = any>(data: T, timeout?: number): {
53
+ abort: () => void;
54
+ response: <U = any>() => Promise<U>;
55
+ };
56
+ /**
57
+ * 推送消息
58
+ * @param data - 消息数据
59
+ * @param timeout - 超时时间
60
+ * @returns 消息响应
61
+ */
62
+ protected _push<T = any>(data: T, timeout?: number): {
63
+ abort: () => void;
64
+ response: <U = any>() => Promise<U>;
65
+ };
66
+ /**
67
+ * 写入消息
68
+ * @param data - 消息数据
69
+ * @param timeout - 超时时间
70
+ * @returns 消息响应
71
+ */
72
+ private _write;
73
+ /**
74
+ * 处理请求消息
75
+ * @param msg - 消息数据
76
+ */
77
+ private onRequest;
78
+ /**
79
+ * 处理响应消息
80
+ * @param msg - 消息数据
81
+ */
82
+ private onResponse;
83
+ /**
84
+ * 接收消息
85
+ * @param msg - 消息数据
86
+ */
87
+ receive(msg: MessageTransferFormat): void;
88
+ }
package/dist/index.js ADDED
@@ -0,0 +1,237 @@
1
+ import { AbortException, Exception, TimeoutException } from "./exception.js";
2
+ export * from './exception.js';
3
+ export var MESSAGE_MODEM_TYPE;
4
+ (function (MESSAGE_MODEM_TYPE) {
5
+ MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["REQUEST"] = 0] = "REQUEST";
6
+ MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["RESPONSE"] = 1] = "RESPONSE";
7
+ MESSAGE_MODEM_TYPE[MESSAGE_MODEM_TYPE["ABORT"] = 2] = "ABORT";
8
+ })(MESSAGE_MODEM_TYPE || (MESSAGE_MODEM_TYPE = {}));
9
+ export class MessageModem {
10
+ id = 0;
11
+ aborts = new Map();
12
+ stacks = new Map();
13
+ /**
14
+ * 创建自增 ID
15
+ * 超过最大安全整数时重置为 0
16
+ * @returns
17
+ */
18
+ createIncrementId() {
19
+ let id = this.id++;
20
+ if (this.id >= Number.MAX_SAFE_INTEGER) {
21
+ id = this.id = 0;
22
+ }
23
+ return id;
24
+ }
25
+ /**
26
+ * 创建发送消息数据
27
+ * @param mode - 消息类型
28
+ * @param data - 消息数据
29
+ * @returns 消息数据
30
+ */
31
+ createPostData(mode, data, twoway = true) {
32
+ const id = this.createIncrementId();
33
+ const state = {
34
+ id, twoway, data, mode,
35
+ };
36
+ if (mode === MESSAGE_MODEM_TYPE.ABORT) {
37
+ state.twoway = false;
38
+ }
39
+ return state;
40
+ }
41
+ /**
42
+ * 发送消息
43
+ * @param data - 消息数据
44
+ * @param timeout - 超时时间
45
+ * @returns 消息响应
46
+ */
47
+ _send(data, timeout = 30000) {
48
+ return this._write(data, timeout, true);
49
+ }
50
+ /**
51
+ * 推送消息
52
+ * @param data - 消息数据
53
+ * @param timeout - 超时时间
54
+ * @returns 消息响应
55
+ */
56
+ _push(data, timeout = 30000) {
57
+ return this._write(data, timeout, false);
58
+ }
59
+ /**
60
+ * 写入消息
61
+ * @param data - 消息数据
62
+ * @param timeout - 超时时间
63
+ * @returns 消息响应
64
+ */
65
+ _write(data, timeout = 30000, twoway = false) {
66
+ const controller = new AbortController();
67
+ // 创建请求消息数据
68
+ const state = this.createPostData(MESSAGE_MODEM_TYPE.REQUEST, data, twoway);
69
+ // 发送消息
70
+ this.post(state);
71
+ return {
72
+ // 终止请求
73
+ abort: () => controller.abort(),
74
+ // 等待响应
75
+ response: () => new Promise((resolve, reject) => {
76
+ // 清理 stacks
77
+ const clear = () => {
78
+ if (this.stacks.has(state.id)) {
79
+ this.stacks.delete(state.id);
80
+ }
81
+ };
82
+ const clean = () => {
83
+ clearTimeout(timer);
84
+ controller.signal.removeEventListener('abort', aborthandler);
85
+ clear();
86
+ };
87
+ // Abort 处理函数
88
+ const aborthandler = () => {
89
+ clearTimeout(timer);
90
+ this.post(this.createPostData(MESSAGE_MODEM_TYPE.ABORT, state.id));
91
+ clear();
92
+ reject(new AbortException());
93
+ };
94
+ // 成功处理
95
+ const _resolve = (data) => {
96
+ clean();
97
+ resolve(data);
98
+ };
99
+ // 失败处理
100
+ const _reject = (e) => {
101
+ clean();
102
+ reject(e);
103
+ };
104
+ // 超时处理
105
+ const timer = setTimeout(() => {
106
+ if (!controller.signal.aborted) {
107
+ controller.abort();
108
+ }
109
+ else {
110
+ _reject(new TimeoutException());
111
+ }
112
+ }, timeout);
113
+ // 添加 Abort 处理函数
114
+ controller.signal.addEventListener('abort', aborthandler);
115
+ // 添加栈
116
+ this.stacks.set(state.id, {
117
+ resolve: _resolve,
118
+ reject: _reject,
119
+ });
120
+ })
121
+ };
122
+ }
123
+ /**
124
+ * 处理请求消息
125
+ * @param msg - 消息数据
126
+ */
127
+ onRequest(msg) {
128
+ // 执行消息
129
+ // 使用 Promise.race 处理消息执行和 Abort 处理
130
+ Promise.race([
131
+ this.exec(msg.data).catch(e => ({ e })),
132
+ new Promise((_, reject) => this.aborts.set(msg.id, reject)),
133
+ ]).then(value => {
134
+ // 如果消息执行失败
135
+ if (value?.e) {
136
+ // 如果消息是双向的,则发送响应消息
137
+ if (msg.twoway) {
138
+ this.post({
139
+ id: msg.id,
140
+ mode: MESSAGE_MODEM_TYPE.RESPONSE,
141
+ twoway: false,
142
+ data: {
143
+ status: value.e instanceof Exception ? value.e.status : 500,
144
+ data: null,
145
+ message: value.e.message,
146
+ }
147
+ });
148
+ }
149
+ }
150
+ else {
151
+ // 如果消息是双向的,则发送响应消息
152
+ if (msg.twoway) {
153
+ this.post({
154
+ id: msg.id,
155
+ mode: MESSAGE_MODEM_TYPE.RESPONSE,
156
+ twoway: false,
157
+ data: {
158
+ status: 200,
159
+ data: value,
160
+ }
161
+ });
162
+ }
163
+ }
164
+ }).catch(e => {
165
+ if (e instanceof AbortException)
166
+ return;
167
+ // 如果消息是双向的,则发送响应消息
168
+ if (msg.twoway) {
169
+ // 发送响应消息
170
+ const code = e instanceof Exception ? e.status : 500;
171
+ this.post({
172
+ id: msg.id,
173
+ mode: MESSAGE_MODEM_TYPE.RESPONSE,
174
+ twoway: false,
175
+ data: {
176
+ status: code,
177
+ data: null,
178
+ message: e.message,
179
+ }
180
+ });
181
+ }
182
+ }).finally(() => {
183
+ // 删除 Abort 处理函数
184
+ if (this.aborts.has(msg.id)) {
185
+ this.aborts.delete(msg.id);
186
+ }
187
+ // 清理栈
188
+ if (this.stacks.has(msg.id)) {
189
+ this.stacks.delete(msg.id);
190
+ }
191
+ });
192
+ }
193
+ /**
194
+ * 处理响应消息
195
+ * @param msg - 消息数据
196
+ */
197
+ onResponse(msg) {
198
+ const id = msg.id;
199
+ const res = msg.data;
200
+ // 如果栈中存在该消息,则处理响应消息
201
+ if (this.stacks.has(id)) {
202
+ const { resolve, reject } = this.stacks.get(id);
203
+ // 如果响应状态码不是 200,则拒绝响应
204
+ if (res?.status !== 200) {
205
+ reject(new Exception(res?.status, res?.message));
206
+ }
207
+ else {
208
+ resolve(res?.data);
209
+ }
210
+ }
211
+ }
212
+ /**
213
+ * 接收消息
214
+ * @param msg - 消息数据
215
+ */
216
+ receive(msg) {
217
+ // 根据消息类型处理消息
218
+ switch (msg.mode) {
219
+ // 处理请求消息
220
+ case MESSAGE_MODEM_TYPE.REQUEST:
221
+ this.onRequest(msg);
222
+ break;
223
+ // 处理响应消息
224
+ case MESSAGE_MODEM_TYPE.RESPONSE:
225
+ this.onResponse(msg);
226
+ break;
227
+ // 处理终止消息
228
+ case MESSAGE_MODEM_TYPE.ABORT:
229
+ const id = msg.data;
230
+ if (this.aborts.has(id)) {
231
+ const reject = this.aborts.get(id);
232
+ reject(new AbortException());
233
+ break;
234
+ }
235
+ }
236
+ }
237
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hile/message-modem",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
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": "41b3df4d124f88453cce2a46a3529dcd6c2480ba"
24
+ "gitHead": "86b1ce38e9eeb069acb2188834a0ff5b466a050a"
25
25
  }