@thejrsoft/subway-protocol 1.5.0 → 1.6.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/CHANGELOG.md +136 -0
- package/dist/asyncapi-sync.js +3 -3
- package/dist/code-meta.d.ts +57 -0
- package/dist/code-meta.d.ts.map +1 -0
- package/dist/code-meta.js +286 -0
- package/dist/code-meta.js.map +1 -0
- package/dist/index.d.ts +51 -24
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +99 -22
- package/dist/index.js.map +1 -1
- package/dist/message-validator.d.ts +1 -5
- package/dist/message-validator.d.ts.map +1 -1
- package/dist/message-validator.js +3 -9
- package/dist/message-validator.js.map +1 -1
- package/dist/protocol-utils.d.ts +6 -6
- package/dist/protocol-utils.js +4 -4
- package/package.json +2 -2
- package/src/asyncapi-sync.ts +4 -4
- package/src/code-meta.ts +341 -0
- package/src/index.ts +148 -27
- package/src/message-validator.ts +4 -12
- package/src/protocol-utils.ts +9 -9
package/src/code-meta.ts
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeMeta — v1.6.0 协议核心
|
|
3
|
+
*
|
|
4
|
+
* report.code 是业务命运的单一信息源;status / level / semantic 全部由此派生。
|
|
5
|
+
*
|
|
6
|
+
* 设备端调用 dispatchMessage(code, data) 时,协议层自动按 code 推导 status / level,
|
|
7
|
+
* 集成方接收消息后调用 resolveCodeMeta(code) 查表得到 semantic / requiresErrorBlock 等。
|
|
8
|
+
*
|
|
9
|
+
* 设计依据:claude-docs/protocol-upgrade-proposal-v1.6.0.md
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// v1.6.0: 使用 type-only import 避免与 index.ts 的循环依赖
|
|
13
|
+
// index.ts 在 MessageFactory.dispatchMessage 中 import code-meta,
|
|
14
|
+
// code-meta 本文件如果运行时 import ./index 会触发 TDZ。
|
|
15
|
+
// 使用 type import 仅在编译期解析,运行时不产生 cyclic require。
|
|
16
|
+
import type { ReportLevel } from './index';
|
|
17
|
+
|
|
18
|
+
/** v1.6.0:单一 status enum,合并 v1.5.0 的 CommandStatus + ProgressStatus */
|
|
19
|
+
export enum MessageStatus {
|
|
20
|
+
IN_PROGRESS = 'IN_PROGRESS',
|
|
21
|
+
COMPLETED = 'COMPLETED',
|
|
22
|
+
FAILED = 'FAILED',
|
|
23
|
+
CANCELLED = 'CANCELLED',
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** 业务语义分类——终态语义 + 进度语义 */
|
|
27
|
+
export type CodeSemantic =
|
|
28
|
+
// 终态语义(仅在 isTerminal=true 时出现)
|
|
29
|
+
| 'success'
|
|
30
|
+
| 'partial'
|
|
31
|
+
| 'failure'
|
|
32
|
+
| 'no-op'
|
|
33
|
+
| 'cancel'
|
|
34
|
+
| 'busy'
|
|
35
|
+
// 进度语义(isTerminal=false)
|
|
36
|
+
| 'phase_start'
|
|
37
|
+
| 'phase_end'
|
|
38
|
+
| 'phase_failed'
|
|
39
|
+
| 'step_success'
|
|
40
|
+
| 'step_skipped'
|
|
41
|
+
| 'step_progress'
|
|
42
|
+
| 'retry'
|
|
43
|
+
| 'retry_pending'
|
|
44
|
+
| 'warning';
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* 单一 code 的元数据描述符
|
|
48
|
+
*
|
|
49
|
+
* 派生字段(status / level)会序列化进消息;
|
|
50
|
+
* 客户端字段(semantic / requiresErrorBlock / isTerminal)仅供查表,不进消息。
|
|
51
|
+
*/
|
|
52
|
+
export interface CodeMeta {
|
|
53
|
+
isTerminal: boolean;
|
|
54
|
+
status: MessageStatus;
|
|
55
|
+
level: ReportLevel;
|
|
56
|
+
semantic: CodeSemantic;
|
|
57
|
+
requiresErrorBlock: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 终态 code 显式 META 表
|
|
62
|
+
*
|
|
63
|
+
* 这些 code 是业务命运决断点,每个语义都是独特的,**不能用后缀规则推导**。
|
|
64
|
+
* 设备端发送终态消息时,dispatchMessage 在此表查询,自动填充消息字段。
|
|
65
|
+
*/
|
|
66
|
+
export const EXPLICIT_META: Record<string, CodeMeta> = {
|
|
67
|
+
// ═══════════ PROGRAM 节目部署 ═══════════
|
|
68
|
+
PROGRAM_COMPLETED: {
|
|
69
|
+
isTerminal: true,
|
|
70
|
+
status: MessageStatus.COMPLETED,
|
|
71
|
+
level: 'INFO' as ReportLevel,
|
|
72
|
+
semantic: 'success',
|
|
73
|
+
requiresErrorBlock: false,
|
|
74
|
+
},
|
|
75
|
+
PROGRAM_PARTIALLY_SUCCEEDED: {
|
|
76
|
+
isTerminal: true,
|
|
77
|
+
status: MessageStatus.COMPLETED,
|
|
78
|
+
level: 'WARNING' as ReportLevel,
|
|
79
|
+
semantic: 'partial',
|
|
80
|
+
requiresErrorBlock: false,
|
|
81
|
+
},
|
|
82
|
+
PROGRAM_ALL_FAILED: {
|
|
83
|
+
isTerminal: true,
|
|
84
|
+
status: MessageStatus.FAILED,
|
|
85
|
+
level: 'ERROR' as ReportLevel,
|
|
86
|
+
semantic: 'failure',
|
|
87
|
+
requiresErrorBlock: true,
|
|
88
|
+
},
|
|
89
|
+
PROGRAM_NO_PROGRAMS: {
|
|
90
|
+
isTerminal: true,
|
|
91
|
+
status: MessageStatus.COMPLETED,
|
|
92
|
+
level: 'INFO' as ReportLevel,
|
|
93
|
+
semantic: 'no-op',
|
|
94
|
+
requiresErrorBlock: false,
|
|
95
|
+
},
|
|
96
|
+
PROGRAM_UPLOAD_FAILED: {
|
|
97
|
+
isTerminal: true,
|
|
98
|
+
status: MessageStatus.FAILED,
|
|
99
|
+
level: 'ERROR' as ReportLevel,
|
|
100
|
+
semantic: 'failure',
|
|
101
|
+
requiresErrorBlock: true,
|
|
102
|
+
},
|
|
103
|
+
PROGRAM_UPLOAD_BUSY: {
|
|
104
|
+
isTerminal: true,
|
|
105
|
+
status: MessageStatus.FAILED,
|
|
106
|
+
level: 'WARNING' as ReportLevel,
|
|
107
|
+
semantic: 'busy',
|
|
108
|
+
requiresErrorBlock: true,
|
|
109
|
+
},
|
|
110
|
+
PROGRAM_UPLOAD_CANCELLED: {
|
|
111
|
+
isTerminal: true,
|
|
112
|
+
status: MessageStatus.CANCELLED,
|
|
113
|
+
level: 'WARNING' as ReportLevel,
|
|
114
|
+
semantic: 'cancel',
|
|
115
|
+
requiresErrorBlock: true,
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// ═══════════ QUICKLY_DETECTION 一键检测 ═══════════
|
|
119
|
+
QUICK_DETECTION_ALL_ONLINE: {
|
|
120
|
+
isTerminal: true,
|
|
121
|
+
status: MessageStatus.COMPLETED,
|
|
122
|
+
level: 'INFO' as ReportLevel,
|
|
123
|
+
semantic: 'success',
|
|
124
|
+
requiresErrorBlock: false,
|
|
125
|
+
},
|
|
126
|
+
QUICK_DETECTION_PARTIAL_ONLINE: {
|
|
127
|
+
isTerminal: true,
|
|
128
|
+
status: MessageStatus.COMPLETED,
|
|
129
|
+
level: 'WARNING' as ReportLevel,
|
|
130
|
+
semantic: 'partial',
|
|
131
|
+
requiresErrorBlock: false,
|
|
132
|
+
},
|
|
133
|
+
QUICK_DETECTION_ALL_OFFLINE: {
|
|
134
|
+
isTerminal: true,
|
|
135
|
+
status: MessageStatus.COMPLETED,
|
|
136
|
+
level: 'ERROR' as ReportLevel,
|
|
137
|
+
semantic: 'failure',
|
|
138
|
+
requiresErrorBlock: true,
|
|
139
|
+
},
|
|
140
|
+
QUICK_DETECTION_FAILED: {
|
|
141
|
+
isTerminal: true,
|
|
142
|
+
status: MessageStatus.FAILED,
|
|
143
|
+
level: 'ERROR' as ReportLevel,
|
|
144
|
+
semantic: 'failure',
|
|
145
|
+
requiresErrorBlock: true,
|
|
146
|
+
},
|
|
147
|
+
QUICK_DETECTION_BUSY: {
|
|
148
|
+
isTerminal: true,
|
|
149
|
+
status: MessageStatus.FAILED,
|
|
150
|
+
level: 'WARNING' as ReportLevel,
|
|
151
|
+
semantic: 'busy',
|
|
152
|
+
requiresErrorBlock: true,
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// ═══════════ SYNC_MONITORING_TABLE 监播导出 ═══════════
|
|
156
|
+
SYNC_MONITORING_TABLE_READ_SUCCESS: {
|
|
157
|
+
isTerminal: true,
|
|
158
|
+
status: MessageStatus.COMPLETED,
|
|
159
|
+
level: 'INFO' as ReportLevel,
|
|
160
|
+
semantic: 'success',
|
|
161
|
+
requiresErrorBlock: false,
|
|
162
|
+
},
|
|
163
|
+
SYNC_MONITORING_TABLE_READ_FAILED: {
|
|
164
|
+
isTerminal: true,
|
|
165
|
+
status: MessageStatus.FAILED,
|
|
166
|
+
level: 'ERROR' as ReportLevel,
|
|
167
|
+
semantic: 'failure',
|
|
168
|
+
requiresErrorBlock: true,
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
// ═══════════ EDGE 代理层合成的终态拒绝 ═══════════
|
|
172
|
+
// 当 Edge 在转发命令到设备的过程中失败(设备不在线、命令格式错、转发失败等),
|
|
173
|
+
// Edge 合成一个终态消息发回 Gateway 让命令生命周期闭环。
|
|
174
|
+
// 具体失败原因通过 data.error.{phase, step, category, detail} 表达。
|
|
175
|
+
EDGE_COMMAND_REJECTED: {
|
|
176
|
+
isTerminal: true,
|
|
177
|
+
status: MessageStatus.FAILED,
|
|
178
|
+
level: 'ERROR' as ReportLevel,
|
|
179
|
+
semantic: 'failure',
|
|
180
|
+
requiresErrorBlock: true,
|
|
181
|
+
},
|
|
182
|
+
EDGE_PROGRAM_REJECTED: {
|
|
183
|
+
isTerminal: true,
|
|
184
|
+
status: MessageStatus.FAILED,
|
|
185
|
+
level: 'ERROR' as ReportLevel,
|
|
186
|
+
semantic: 'failure',
|
|
187
|
+
requiresErrorBlock: true,
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* 进度 code 后缀规则
|
|
193
|
+
*
|
|
194
|
+
* 进度消息有数百个 code(按 PROGRAM_FETCH_DOWNLOAD_PROGRESS 这种命名规范),
|
|
195
|
+
* 不可能逐一罗列 META,但 level / status / semantic 完全可从后缀派生。
|
|
196
|
+
*
|
|
197
|
+
* 优先级从高到低,首次命中即返回。
|
|
198
|
+
*/
|
|
199
|
+
interface SuffixRule {
|
|
200
|
+
/** 后缀匹配(endsWith)*/
|
|
201
|
+
suffix?: string;
|
|
202
|
+
/** 中缀匹配(用于 _WARN_ 这种夹中间的情况)*/
|
|
203
|
+
infix?: string;
|
|
204
|
+
meta: Omit<CodeMeta, 'isTerminal'>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const SUFFIX_RULES: SuffixRule[] = [
|
|
208
|
+
{
|
|
209
|
+
suffix: '_FAILED',
|
|
210
|
+
meta: {
|
|
211
|
+
status: MessageStatus.IN_PROGRESS,
|
|
212
|
+
level: 'ERROR' as ReportLevel,
|
|
213
|
+
semantic: 'phase_failed',
|
|
214
|
+
requiresErrorBlock: true,
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
suffix: '_RETRY_PENDING',
|
|
219
|
+
meta: {
|
|
220
|
+
status: MessageStatus.IN_PROGRESS,
|
|
221
|
+
level: 'WARNING' as ReportLevel,
|
|
222
|
+
semantic: 'retry_pending',
|
|
223
|
+
requiresErrorBlock: false,
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
suffix: '_RETRY',
|
|
228
|
+
meta: {
|
|
229
|
+
status: MessageStatus.IN_PROGRESS,
|
|
230
|
+
level: 'WARNING' as ReportLevel,
|
|
231
|
+
semantic: 'retry',
|
|
232
|
+
requiresErrorBlock: false,
|
|
233
|
+
},
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
// 后缀或中缀都命中:例 PROGRAM_UPLOAD_WARN_CAN_BUS_ALARM
|
|
237
|
+
infix: '_WARN',
|
|
238
|
+
meta: {
|
|
239
|
+
status: MessageStatus.IN_PROGRESS,
|
|
240
|
+
level: 'WARNING' as ReportLevel,
|
|
241
|
+
semantic: 'warning',
|
|
242
|
+
requiresErrorBlock: false,
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
suffix: '_SKIPPED',
|
|
247
|
+
meta: {
|
|
248
|
+
status: MessageStatus.IN_PROGRESS,
|
|
249
|
+
level: 'INFO' as ReportLevel,
|
|
250
|
+
semantic: 'step_skipped',
|
|
251
|
+
requiresErrorBlock: false,
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
suffix: '_COMPLETED',
|
|
256
|
+
meta: {
|
|
257
|
+
status: MessageStatus.IN_PROGRESS,
|
|
258
|
+
level: 'INFO' as ReportLevel,
|
|
259
|
+
semantic: 'phase_end',
|
|
260
|
+
requiresErrorBlock: false,
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
suffix: '_START',
|
|
265
|
+
meta: {
|
|
266
|
+
status: MessageStatus.IN_PROGRESS,
|
|
267
|
+
level: 'INFO' as ReportLevel,
|
|
268
|
+
semantic: 'phase_start',
|
|
269
|
+
requiresErrorBlock: false,
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
suffix: '_PROGRESS',
|
|
274
|
+
meta: {
|
|
275
|
+
status: MessageStatus.IN_PROGRESS,
|
|
276
|
+
level: 'INFO' as ReportLevel,
|
|
277
|
+
semantic: 'step_progress',
|
|
278
|
+
requiresErrorBlock: false,
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
suffix: '_SUCCESS',
|
|
283
|
+
meta: {
|
|
284
|
+
status: MessageStatus.IN_PROGRESS,
|
|
285
|
+
level: 'INFO' as ReportLevel,
|
|
286
|
+
semantic: 'step_success',
|
|
287
|
+
requiresErrorBlock: false,
|
|
288
|
+
},
|
|
289
|
+
},
|
|
290
|
+
];
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* 兜底 META(未知 code 时返回)
|
|
294
|
+
*
|
|
295
|
+
* 接收方收到未知 code 时降级到 step_progress,避免阻塞协议处理。
|
|
296
|
+
*/
|
|
297
|
+
const FALLBACK_META: CodeMeta = {
|
|
298
|
+
isTerminal: false,
|
|
299
|
+
status: MessageStatus.IN_PROGRESS,
|
|
300
|
+
level: 'INFO' as ReportLevel,
|
|
301
|
+
semantic: 'step_progress',
|
|
302
|
+
requiresErrorBlock: false,
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* 单一 code 解析入口
|
|
307
|
+
*
|
|
308
|
+
* 解析顺序:
|
|
309
|
+
* 1. 终态显式表(EXPLICIT_META) — 30 个 closed enum
|
|
310
|
+
* 2. 后缀规则(SUFFIX_RULES) — 9 条规则覆盖数百个进度 code
|
|
311
|
+
* 3. 兜底(FALLBACK_META) — 未知 code 降级
|
|
312
|
+
*
|
|
313
|
+
* @param code report.code 字符串
|
|
314
|
+
* @returns CodeMeta — status / level / semantic / requiresErrorBlock / isTerminal
|
|
315
|
+
*/
|
|
316
|
+
export function resolveCodeMeta(code: string): CodeMeta {
|
|
317
|
+
if (EXPLICIT_META[code]) {
|
|
318
|
+
return EXPLICIT_META[code];
|
|
319
|
+
}
|
|
320
|
+
for (const rule of SUFFIX_RULES) {
|
|
321
|
+
if (rule.suffix && code.endsWith(rule.suffix)) {
|
|
322
|
+
return { isTerminal: false, ...rule.meta };
|
|
323
|
+
}
|
|
324
|
+
if (rule.infix && code.includes(rule.infix)) {
|
|
325
|
+
return { isTerminal: false, ...rule.meta };
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
return FALLBACK_META;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* 判断 code 是否已知(命中显式表或后缀规则,不走兜底)
|
|
333
|
+
*/
|
|
334
|
+
export function isKnownCode(code: string): boolean {
|
|
335
|
+
if (EXPLICIT_META[code]) return true;
|
|
336
|
+
for (const rule of SUFFIX_RULES) {
|
|
337
|
+
if (rule.suffix && code.endsWith(rule.suffix)) return true;
|
|
338
|
+
if (rule.infix && code.includes(rule.infix)) return true;
|
|
339
|
+
}
|
|
340
|
+
return false;
|
|
341
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* JRSoft Subway 统一WebSocket协议定义
|
|
3
3
|
* 版本: 1.0
|
|
4
|
-
*
|
|
4
|
+
*
|
|
5
5
|
* 功能特性:
|
|
6
6
|
* 1. 统一消息格式定义
|
|
7
7
|
* 2. 支持设备注册、命令执行、心跳等核心功能
|
|
8
8
|
* 3. 支持 Edge 代理模式
|
|
9
|
+
*
|
|
10
|
+
* v1.6.0:单一 MessageStatus + CodeMeta + dispatchMessage(详见 code-meta.ts)
|
|
9
11
|
*/
|
|
10
12
|
|
|
13
|
+
// v1.6.0:从 code-meta 模块导入工具(用于 MessageFactory.dispatchMessage)
|
|
14
|
+
import { resolveCodeMeta, MessageStatus } from './code-meta';
|
|
15
|
+
|
|
11
16
|
// 消息类型枚举
|
|
12
17
|
export enum MessageType {
|
|
13
18
|
// 连接管理
|
|
@@ -74,14 +79,11 @@ export enum Priority {
|
|
|
74
79
|
EMERGENCY = 'EMERGENCY'
|
|
75
80
|
}
|
|
76
81
|
|
|
77
|
-
//
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
CANCELLED = 'CANCELLED',
|
|
83
|
-
IN_PROGRESS = 'IN_PROGRESS' // Gateway特有
|
|
84
|
-
}
|
|
82
|
+
// v1.6.0: 单一 MessageStatus enum 取代 CommandStatus / ProgressStatus
|
|
83
|
+
// 字符串值与 v1.5.0 重叠的 4 值保持一致:'IN_PROGRESS' / 'COMPLETED' / 'FAILED' / 'CANCELLED'
|
|
84
|
+
// 死代码值删除:CommandStatus.TIMEOUT / ProgressStatus.PENDING / PAUSED
|
|
85
|
+
// TIMEOUT 语义迁移:status=FAILED + report.data.error.category='TIMEOUT'
|
|
86
|
+
export { MessageStatus, type CodeMeta, type CodeSemantic, resolveCodeMeta, isKnownCode, EXPLICIT_META } from './code-meta';
|
|
85
87
|
|
|
86
88
|
// 基础消息接口
|
|
87
89
|
export interface BaseMessage {
|
|
@@ -288,7 +290,7 @@ export interface CommandResponseMessage extends BaseMessage {
|
|
|
288
290
|
type: MessageType.COMMAND_RESPONSE;
|
|
289
291
|
clientId: string; // 响应设备标识
|
|
290
292
|
requestRef: string;
|
|
291
|
-
status:
|
|
293
|
+
status: MessageStatus;
|
|
292
294
|
result?: CommandResult; // 命令执行结果
|
|
293
295
|
report?: ReportMessage;
|
|
294
296
|
executionTime?: number;
|
|
@@ -381,7 +383,7 @@ export interface ProgramResponseMessage extends BaseMessage {
|
|
|
381
383
|
type: MessageType.PROGRAM_RESPONSE;
|
|
382
384
|
clientId: string; // 响应设备标识
|
|
383
385
|
requestRef: string;
|
|
384
|
-
status:
|
|
386
|
+
status: MessageStatus; // v1.6.0 统一 status enum
|
|
385
387
|
context?: ProgramContext; // 上下文信息(可选)
|
|
386
388
|
report?: ReportMessage; // 日志信息(可选)
|
|
387
389
|
executionTime?: number; // 总执行时间(毫秒)
|
|
@@ -446,15 +448,9 @@ export interface DeviceOperationRecord {
|
|
|
446
448
|
result?: Record<string, any>; // 命令执行结果对象
|
|
447
449
|
}
|
|
448
450
|
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
IN_PROGRESS = 'IN_PROGRESS',
|
|
453
|
-
PAUSED = 'PAUSED',
|
|
454
|
-
COMPLETED = 'COMPLETED',
|
|
455
|
-
FAILED = 'FAILED',
|
|
456
|
-
CANCELLED = 'CANCELLED'
|
|
457
|
-
}
|
|
451
|
+
// v1.6.0:原 ProgressStatus enum 删除——所有消息的 status 字段统一类型为 MessageStatus
|
|
452
|
+
// 删除的死代码值:PENDING / PAUSED(两个值在 v1.5.0 内 0 引用,全协议历史从未真实发出)
|
|
453
|
+
// 集成方:import { MessageStatus } from '@thejrsoft/subway-protocol'
|
|
458
454
|
|
|
459
455
|
// 程序上下文信息
|
|
460
456
|
export interface ProgramContext {
|
|
@@ -511,9 +507,11 @@ export type ErrorPhase =
|
|
|
511
507
|
| 'SYNC_RECOVER'
|
|
512
508
|
| 'DETECT_COMPLETE'
|
|
513
509
|
| 'SYNC_EXPORT'
|
|
514
|
-
| 'BATCH_EXECUTE'
|
|
510
|
+
| 'BATCH_EXECUTE'
|
|
511
|
+
| 'EDGE_PROXY';
|
|
515
512
|
|
|
516
513
|
// v1.5.0: ErrorStep — 21 个值,按 phase 归属(详见 message-validator.ts 的 step-phase 映射)
|
|
514
|
+
// v1.6.1:Edge 代理层新增 4 个 step(适用于 EDGE_PROXY phase)
|
|
517
515
|
export type ErrorStep =
|
|
518
516
|
// PROGRAM_INIT
|
|
519
517
|
| 'ValidateParameters'
|
|
@@ -542,7 +540,12 @@ export type ErrorStep =
|
|
|
542
540
|
| 'StatusRecovery'
|
|
543
541
|
| 'RetryLimitExceeded'
|
|
544
542
|
// PROGRAM_STATS
|
|
545
|
-
| 'CloudReport'
|
|
543
|
+
| 'CloudReport'
|
|
544
|
+
// EDGE_PROXY(v1.6.1 新增)
|
|
545
|
+
| 'ValidateCommandFormat'
|
|
546
|
+
| 'CheckDeviceOnline'
|
|
547
|
+
| 'DispatchToDevice'
|
|
548
|
+
| 'AwaitDeviceResponse';
|
|
546
549
|
|
|
547
550
|
// v1.5.0: 统一失败信号 schema — 嵌套在 report.data.error 下,仅 ERROR / 终态失败消息携带
|
|
548
551
|
// 4 字段全部必填,3 个 enum 字段在服务端做 strict 校验(拒收未定义值)
|
|
@@ -576,7 +579,7 @@ export interface ProgressUpdateMessage extends BaseMessage {
|
|
|
576
579
|
type: MessageType.PROGRESS_UPDATE;
|
|
577
580
|
clientId: string; // 上报设备标识
|
|
578
581
|
requestRef: string;
|
|
579
|
-
status:
|
|
582
|
+
status: MessageStatus; // v1.6.0 统一 status enum(进度消息一律 IN_PROGRESS)
|
|
580
583
|
phase: ProgressPhase | string; // 当前阶段(支持自定义阶段)
|
|
581
584
|
progress: number; // 0-100 进度百分比
|
|
582
585
|
sourceType: ProgressSourceType; // 来源类型:COMMAND/SYSTEM/EDGE(v1.4.11 加 EDGE)
|
|
@@ -710,6 +713,8 @@ export const ERROR_STEP_BY_PHASE: Record<string, readonly ErrorStep[]> = {
|
|
|
710
713
|
PROGRAM_UPLOAD: ['DeviceCheck', 'DataTransfer', 'ForbiddenTable', 'StatusRecovery', 'RetryLimitExceeded'] as const,
|
|
711
714
|
PROGRAM_STATS: ['CloudReport'] as const,
|
|
712
715
|
PROGRAM_COMPLETE: [] as const,
|
|
716
|
+
// v1.6.1:Edge 代理层 4 个 step
|
|
717
|
+
EDGE_PROXY: ['ValidateCommandFormat', 'CheckDeviceOnline', 'DispatchToDevice', 'AwaitDeviceResponse'] as const,
|
|
713
718
|
// QUICKLY_DETECTION / SYNC_MONITORING_TABLE 在 v1.5.x 后续补充 step 归属
|
|
714
719
|
} as const;
|
|
715
720
|
|
|
@@ -888,7 +893,7 @@ export class MessageFactory {
|
|
|
888
893
|
phase: ProgressPhase | string,
|
|
889
894
|
progress: number,
|
|
890
895
|
message?: string,
|
|
891
|
-
status:
|
|
896
|
+
status: MessageStatus = MessageStatus.IN_PROGRESS,
|
|
892
897
|
options?: {
|
|
893
898
|
level?: ReportLevel;
|
|
894
899
|
code?: string;
|
|
@@ -924,7 +929,7 @@ export class MessageFactory {
|
|
|
924
929
|
static createCommandResponseMessage(
|
|
925
930
|
clientId: string, // 添加:响应设备标识
|
|
926
931
|
requestRef: string,
|
|
927
|
-
status:
|
|
932
|
+
status: MessageStatus,
|
|
928
933
|
result: any,
|
|
929
934
|
message?: string,
|
|
930
935
|
options?: {
|
|
@@ -960,7 +965,7 @@ export class MessageFactory {
|
|
|
960
965
|
static createProgramResponseMessage(
|
|
961
966
|
clientId: string, // 添加:响应设备标识
|
|
962
967
|
requestRef: string,
|
|
963
|
-
status:
|
|
968
|
+
status: MessageStatus,
|
|
964
969
|
result: any,
|
|
965
970
|
message?: string,
|
|
966
971
|
options?: {
|
|
@@ -1170,6 +1175,122 @@ export class MessageFactory {
|
|
|
1170
1175
|
version: '1.0'
|
|
1171
1176
|
};
|
|
1172
1177
|
}
|
|
1178
|
+
|
|
1179
|
+
/**
|
|
1180
|
+
* v1.6.0: 统一消息派发接口
|
|
1181
|
+
*
|
|
1182
|
+
* 设备端发送终态消息(COMMAND_RESPONSE / PROGRAM_RESPONSE)或进度消息(PROGRESS_UPDATE)时,
|
|
1183
|
+
* 只需要提供 code 和业务字段,协议层通过 CodeMeta 自动推导 status / level / 消息类型。
|
|
1184
|
+
*
|
|
1185
|
+
* 替代旧的 createCommandResponseMessage / createProgramResponseMessage / createProgressUpdateMessage。
|
|
1186
|
+
*
|
|
1187
|
+
* @param params dispatch 参数
|
|
1188
|
+
* @returns 完整的协议消息(具体类型由 code 派生的 isTerminal 决定)
|
|
1189
|
+
* @throws Error 当 code 要求 data.error 但 params.error 缺失时
|
|
1190
|
+
*/
|
|
1191
|
+
static dispatchMessage(params: {
|
|
1192
|
+
clientId: string;
|
|
1193
|
+
requestRef: string;
|
|
1194
|
+
code: string;
|
|
1195
|
+
message: string;
|
|
1196
|
+
/** 终态消息:result 块;进度消息:业务平铺字段 */
|
|
1197
|
+
data?: Record<string, any>;
|
|
1198
|
+
/** v1.5.0 失败 schema — 当 code 要求时必填 */
|
|
1199
|
+
error?: {
|
|
1200
|
+
phase: string;
|
|
1201
|
+
step: string;
|
|
1202
|
+
category: string;
|
|
1203
|
+
detail: string;
|
|
1204
|
+
};
|
|
1205
|
+
/** 仅进度消息:phase 与 progress */
|
|
1206
|
+
phase?: ProgressPhase | string;
|
|
1207
|
+
progress?: number;
|
|
1208
|
+
/** 进度消息 sourceType(默认 COMMAND)*/
|
|
1209
|
+
sourceType?: ProgressSourceType;
|
|
1210
|
+
/** 终态消息 result 块(COMMAND_RESPONSE)*/
|
|
1211
|
+
result?: any;
|
|
1212
|
+
/** 终态消息 context(PROGRAM_RESPONSE)*/
|
|
1213
|
+
context?: ProgramContext;
|
|
1214
|
+
/** 终态消息 executionTime */
|
|
1215
|
+
executionTime?: number;
|
|
1216
|
+
}): CommandResponseMessage | ProgramResponseMessage | ProgressUpdateMessage {
|
|
1217
|
+
const meta = resolveCodeMeta(params.code);
|
|
1218
|
+
|
|
1219
|
+
// 强校验:requiresErrorBlock 时 data.error 必填
|
|
1220
|
+
if (meta.requiresErrorBlock && !params.error) {
|
|
1221
|
+
throw new Error(
|
|
1222
|
+
`dispatchMessage: code "${params.code}" requires data.error (4 fields: phase/step/category/detail)`,
|
|
1223
|
+
);
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
const reportData: Record<string, any> = { ...(params.data || {}) };
|
|
1227
|
+
if (params.error) {
|
|
1228
|
+
reportData.error = params.error;
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
const report: ReportMessage = {
|
|
1232
|
+
level: meta.level,
|
|
1233
|
+
message: params.message,
|
|
1234
|
+
code: params.code,
|
|
1235
|
+
data: reportData,
|
|
1236
|
+
};
|
|
1237
|
+
|
|
1238
|
+
const timestamp = new Date().toISOString();
|
|
1239
|
+
const version = '1.0';
|
|
1240
|
+
|
|
1241
|
+
if (meta.isTerminal) {
|
|
1242
|
+
// 通过 code 前缀决定终态消息类型:PROGRAM_* → PROGRAM_RESPONSE,其余 → COMMAND_RESPONSE
|
|
1243
|
+
const isProgramTerminal =
|
|
1244
|
+
params.code.startsWith('PROGRAM_') && !params.code.startsWith('PROGRAM_FETCH') &&
|
|
1245
|
+
!params.code.startsWith('PROGRAM_EXTRACT') && !params.code.startsWith('PROGRAM_PREPROCESS') &&
|
|
1246
|
+
!params.code.startsWith('PROGRAM_COMPILE') && !params.code.startsWith('PROGRAM_STATS') &&
|
|
1247
|
+
!params.code.startsWith('PROGRAM_INIT');
|
|
1248
|
+
|
|
1249
|
+
if (isProgramTerminal) {
|
|
1250
|
+
const msg: ProgramResponseMessage = {
|
|
1251
|
+
type: MessageType.PROGRAM_RESPONSE,
|
|
1252
|
+
clientId: params.clientId,
|
|
1253
|
+
requestRef: params.requestRef,
|
|
1254
|
+
status: meta.status as MessageStatus,
|
|
1255
|
+
context: params.context,
|
|
1256
|
+
report,
|
|
1257
|
+
executionTime: params.executionTime,
|
|
1258
|
+
timestamp,
|
|
1259
|
+
version,
|
|
1260
|
+
};
|
|
1261
|
+
return msg;
|
|
1262
|
+
} else {
|
|
1263
|
+
const msg: CommandResponseMessage = {
|
|
1264
|
+
type: MessageType.COMMAND_RESPONSE,
|
|
1265
|
+
clientId: params.clientId,
|
|
1266
|
+
requestRef: params.requestRef,
|
|
1267
|
+
status: meta.status as MessageStatus,
|
|
1268
|
+
result: params.result,
|
|
1269
|
+
report,
|
|
1270
|
+
executionTime: params.executionTime,
|
|
1271
|
+
timestamp,
|
|
1272
|
+
version,
|
|
1273
|
+
};
|
|
1274
|
+
return msg;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
// 进度消息
|
|
1279
|
+
const progressMsg: ProgressUpdateMessage = {
|
|
1280
|
+
type: MessageType.PROGRESS_UPDATE,
|
|
1281
|
+
clientId: params.clientId,
|
|
1282
|
+
requestRef: params.requestRef,
|
|
1283
|
+
status: meta.status as MessageStatus,
|
|
1284
|
+
phase: params.phase ?? '',
|
|
1285
|
+
progress: params.progress ?? 0,
|
|
1286
|
+
sourceType: params.sourceType ?? 'COMMAND',
|
|
1287
|
+
context: params.context,
|
|
1288
|
+
report,
|
|
1289
|
+
timestamp,
|
|
1290
|
+
version,
|
|
1291
|
+
};
|
|
1292
|
+
return progressMsg;
|
|
1293
|
+
}
|
|
1173
1294
|
}
|
|
1174
1295
|
|
|
1175
1296
|
|
|
@@ -1196,7 +1317,7 @@ export type AnyMessage =
|
|
|
1196
1317
|
| ErrorMessage;
|
|
1197
1318
|
|
|
1198
1319
|
// 导出常量
|
|
1199
|
-
export const PROTOCOL_VERSION = '1.
|
|
1320
|
+
export const PROTOCOL_VERSION = '1.6.1';
|
|
1200
1321
|
export const DEFAULT_TIMEOUT = 10000;
|
|
1201
1322
|
export const DEFAULT_PRIORITY = Priority.NORMAL;
|
|
1202
1323
|
|
package/src/message-validator.ts
CHANGED
|
@@ -6,10 +6,9 @@ import {
|
|
|
6
6
|
MessageType,
|
|
7
7
|
ClientType,
|
|
8
8
|
Priority,
|
|
9
|
-
|
|
9
|
+
MessageStatus,
|
|
10
10
|
OperationType,
|
|
11
11
|
CommandType,
|
|
12
|
-
ProgressStatus,
|
|
13
12
|
ProgressPhase,
|
|
14
13
|
ReportLevel,
|
|
15
14
|
ProgramType,
|
|
@@ -336,7 +335,7 @@ export class MessageValidator {
|
|
|
336
335
|
}
|
|
337
336
|
if (!message.status) {
|
|
338
337
|
errors.push('status is required');
|
|
339
|
-
} else if (!this.
|
|
338
|
+
} else if (!this.isValidMessageStatus(message.status)) {
|
|
340
339
|
errors.push(`Invalid status: ${message.status}`);
|
|
341
340
|
}
|
|
342
341
|
if (!message.phase) {
|
|
@@ -536,8 +535,8 @@ export class MessageValidator {
|
|
|
536
535
|
/**
|
|
537
536
|
* 验证命令状态
|
|
538
537
|
*/
|
|
539
|
-
static
|
|
540
|
-
return Object.values(
|
|
538
|
+
static isValidMessageStatus(status: string): boolean {
|
|
539
|
+
return Object.values(MessageStatus).includes(status as MessageStatus);
|
|
541
540
|
}
|
|
542
541
|
|
|
543
542
|
/**
|
|
@@ -554,13 +553,6 @@ export class MessageValidator {
|
|
|
554
553
|
return Object.values(CommandType).includes(commandType as CommandType);
|
|
555
554
|
}
|
|
556
555
|
|
|
557
|
-
/**
|
|
558
|
-
* 验证进度状态
|
|
559
|
-
*/
|
|
560
|
-
static isValidProgressStatus(status: string): boolean {
|
|
561
|
-
return Object.values(ProgressStatus).includes(status as ProgressStatus);
|
|
562
|
-
}
|
|
563
|
-
|
|
564
556
|
/**
|
|
565
557
|
* 验证进度阶段
|
|
566
558
|
*/
|