@thejrsoft/subway-protocol 1.3.0

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.
Files changed (78) hide show
  1. package/ACK_MESSAGES_IMPLEMENTATION_SUMMARY.md +128 -0
  2. package/ACK_MESSAGE_DESIGN.md +457 -0
  3. package/CHANGELOG.md +58 -0
  4. package/COMMAND_VALIDATION_RULES.md +178 -0
  5. package/DOCUMENTATION_REORGANIZATION_SUMMARY.md +81 -0
  6. package/DOCUMENTATION_STRUCTURE.md +106 -0
  7. package/GATEWAY_MIGRATION_GUIDE.md +130 -0
  8. package/GATEWAY_PROTOCOL_COMPARISON.md +216 -0
  9. package/INTEGRATION_GUIDE.md +190 -0
  10. package/OPTIONAL_FIELDS_WITHOUT_DEFAULTS.md +97 -0
  11. package/PROTOCOL_UTILS_USAGE.md +278 -0
  12. package/README.md +237 -0
  13. package/TYPE_FIXES_SUMMARY.md +210 -0
  14. package/UPDATE_ENUM_VALUES.md +139 -0
  15. package/dist/asyncapi-sync.d.ts +47 -0
  16. package/dist/asyncapi-sync.d.ts.map +1 -0
  17. package/dist/asyncapi-sync.js +85 -0
  18. package/dist/asyncapi-sync.js.map +1 -0
  19. package/dist/command-factory.d.ts +62 -0
  20. package/dist/command-factory.d.ts.map +1 -0
  21. package/dist/command-factory.js +137 -0
  22. package/dist/command-factory.js.map +1 -0
  23. package/dist/command-types.d.ts +27 -0
  24. package/dist/command-types.d.ts.map +1 -0
  25. package/dist/command-types.js +31 -0
  26. package/dist/command-types.js.map +1 -0
  27. package/dist/index.d.ts +403 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +413 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/message-validator.d.ts +102 -0
  32. package/dist/message-validator.d.ts.map +1 -0
  33. package/dist/message-validator.js +640 -0
  34. package/dist/message-validator.js.map +1 -0
  35. package/dist/protocol-utils.d.ts +108 -0
  36. package/dist/protocol-utils.d.ts.map +1 -0
  37. package/dist/protocol-utils.js +293 -0
  38. package/dist/protocol-utils.js.map +1 -0
  39. package/docs/01-protocol/README.md +45 -0
  40. package/docs/01-protocol/design-rationale.md +198 -0
  41. package/docs/01-protocol/message-types.md +669 -0
  42. package/docs/01-protocol/specification.md +1466 -0
  43. package/docs/02-commands/README.md +56 -0
  44. package/docs/02-commands/batch-command.md +435 -0
  45. package/docs/02-commands/complex-command.md +537 -0
  46. package/docs/02-commands/simple-command.md +332 -0
  47. package/docs/02-commands/typed-commands.md +362 -0
  48. package/docs/03-architecture/README.md +66 -0
  49. package/docs/03-architecture/device-protocol.md +430 -0
  50. package/docs/03-architecture/edge-proxy.md +727 -0
  51. package/docs/03-architecture/routing-flow.md +893 -0
  52. package/docs/04-integration/README.md +144 -0
  53. package/docs/04-integration/backend-guide.md +551 -0
  54. package/docs/04-integration/edge-guide.md +684 -0
  55. package/docs/04-integration/gateway-guide.md +180 -0
  56. package/docs/04-integration/migration-guide.md +226 -0
  57. package/docs/05-examples/README.md +141 -0
  58. package/docs/05-examples/progress-update-examples.md +757 -0
  59. package/docs/06-reference/README.md +67 -0
  60. package/docs/06-reference/api.md +572 -0
  61. package/docs/06-reference/faq.md +302 -0
  62. package/docs/06-reference/glossary.md +232 -0
  63. package/examples/backend-upgrade.ts +279 -0
  64. package/examples/edge-multi-device.ts +513 -0
  65. package/examples/gateway-upgrade.ts +150 -0
  66. package/examples/protocol-implementation.ts +715 -0
  67. package/package.json +48 -0
  68. package/scripts/validate-asyncapi.ts +78 -0
  69. package/src/__tests__/protocol.test.ts +297 -0
  70. package/src/asyncapi-sync.ts +84 -0
  71. package/src/command-factory.ts +183 -0
  72. package/src/command-types.ts +72 -0
  73. package/src/edge-proxy.ts +494 -0
  74. package/src/gateway-extensions.ts +278 -0
  75. package/src/index.ts +792 -0
  76. package/src/message-validator.ts +726 -0
  77. package/src/protocol-utils.ts +355 -0
  78. package/tsconfig.json +24 -0
@@ -0,0 +1,355 @@
1
+ /**
2
+ * 协议工具类 - 提供状态转换、类型判断和请求引用管理功能
3
+ */
4
+
5
+ import {
6
+ CommandStatus,
7
+ Priority,
8
+ ClientType,
9
+ MessageType,
10
+ AnyMessage,
11
+ CommandMessage,
12
+ ProgressUpdateMessage,
13
+ ProgramMessage,
14
+ RegisterMessage,
15
+ HeartbeatMessage,
16
+ ErrorMessage,
17
+ CommandResponseMessage,
18
+ ProgramResponseMessage,
19
+ isCommandMessage as isCommandMsg,
20
+ isProgressUpdateMessage as isProgressMsg,
21
+ isProgramMessage as isProgramMsg,
22
+ } from './index';
23
+
24
+ /**
25
+ * 协议工具类
26
+ */
27
+ export class ProtocolUtils {
28
+ /**
29
+ * 将 CommandStatus 枚举转换为字符串
30
+ */
31
+ static statusToString(status: CommandStatus): string {
32
+ return status.toString();
33
+ }
34
+
35
+ /**
36
+ * 将字符串转换为 CommandStatus 枚举
37
+ * @throws {Error} 如果字符串不是有效的状态值
38
+ */
39
+ static stringToStatus(status: string): CommandStatus {
40
+ // 处理大小写和空格
41
+ const normalizedStatus = status.trim().toUpperCase();
42
+
43
+ // 检查是否是有效的枚举值
44
+ if (!Object.values(CommandStatus).includes(normalizedStatus as CommandStatus)) {
45
+ throw new Error(`Invalid command status: ${status}. Valid values are: ${Object.values(CommandStatus).join(', ')}`);
46
+ }
47
+
48
+ return normalizedStatus as CommandStatus;
49
+ }
50
+
51
+ /**
52
+ * 将字符串转换为 Priority 枚举
53
+ * @throws {Error} 如果字符串不是有效的优先级值
54
+ */
55
+ static priorityToEnum(priority: string): Priority {
56
+ const normalizedPriority = priority.trim().toUpperCase();
57
+
58
+ if (!Object.values(Priority).includes(normalizedPriority as Priority)) {
59
+ throw new Error(`Invalid priority: ${priority}. Valid values are: ${Object.values(Priority).join(', ')}`);
60
+ }
61
+
62
+ return normalizedPriority as Priority;
63
+ }
64
+
65
+ /**
66
+ * 将 Priority 枚举转换为字符串
67
+ */
68
+ static enumToPriority(priority: Priority): string {
69
+ return priority.toString();
70
+ }
71
+
72
+ /**
73
+ * 将字符串转换为 ClientType 枚举
74
+ * @throws {Error} 如果字符串不是有效的客户端类型
75
+ */
76
+ static clientTypeToEnum(clientType: string): ClientType {
77
+ const normalizedType = clientType.trim().toUpperCase();
78
+
79
+ if (!Object.values(ClientType).includes(normalizedType as ClientType)) {
80
+ throw new Error(`Invalid client type: ${clientType}. Valid values are: ${Object.values(ClientType).join(', ')}`);
81
+ }
82
+
83
+ return normalizedType as ClientType;
84
+ }
85
+
86
+ /**
87
+ * 将 ClientType 枚举转换为字符串
88
+ */
89
+ static enumToClientType(clientType: ClientType): string {
90
+ return clientType.toString();
91
+ }
92
+
93
+ /**
94
+ * 检查消息是否为命令消息
95
+ * 增强版本:不仅检查类型,还验证结构
96
+ */
97
+ static isCommandMessage(message: any): message is CommandMessage {
98
+ if (!isCommandMsg(message)) {
99
+ return false;
100
+ }
101
+
102
+ // 额外的结构验证
103
+ return !!(
104
+ message.requestRef &&
105
+ message.targetClientId &&
106
+ message.command &&
107
+ message.priority &&
108
+ message.timeout !== undefined &&
109
+ message.callback
110
+ );
111
+ }
112
+
113
+ /**
114
+ * 检查消息是否为进度更新消息
115
+ * 增强版本:包含额外的结构验证
116
+ */
117
+ static isProgressUpdate(message: any): message is ProgressUpdateMessage {
118
+ if (!isProgressMsg(message)) {
119
+ return false;
120
+ }
121
+
122
+ // 额外的结构验证
123
+ return !!(
124
+ message.requestRef &&
125
+ message.status &&
126
+ message.phase &&
127
+ typeof message.progress === 'number' &&
128
+ message.sourceType &&
129
+ (message.sourceType === 'COMMAND' || message.sourceType === 'SYSTEM')
130
+ );
131
+ }
132
+
133
+ /**
134
+ * 检查消息是否为程序消息
135
+ * 增强版本:包含额外的结构验证
136
+ */
137
+ static isProgramMessage(message: any): message is ProgramMessage {
138
+ if (!isProgramMsg(message)) {
139
+ return false;
140
+ }
141
+
142
+ // 额外的结构验证
143
+ return !!(
144
+ message.requestRef &&
145
+ message.targetClientId &&
146
+ message.command &&
147
+ message.command.commandCode === 'UPLOAD_PROGRAM' &&
148
+ message.command.parameters &&
149
+ message.priority &&
150
+ message.timeout !== undefined &&
151
+ message.callback
152
+ );
153
+ }
154
+
155
+ /**
156
+ * 从任意消息中提取请求引用
157
+ * @returns 请求引用,如果消息类型不包含请求引用则返回 undefined
158
+ */
159
+ static extractRequestRef(message: AnyMessage): string | undefined {
160
+ switch (message.type) {
161
+ // 包含 requestRef 的消息类型
162
+ case MessageType.COMMAND:
163
+ case MessageType.COMMAND_RESPONSE:
164
+ case MessageType.PROGRAM:
165
+ case MessageType.PROGRAM_RESPONSE:
166
+ case MessageType.PROGRESS_UPDATE:
167
+ return (message as any).requestRef;
168
+
169
+ // ERROR 消息可能在 context 中包含 requestRef
170
+ case MessageType.ERROR:
171
+ const errorMsg = message as ErrorMessage;
172
+ if (errorMsg.context && typeof errorMsg.context === 'object') {
173
+ return errorMsg.context.requestRef;
174
+ }
175
+ return undefined;
176
+
177
+ // 其他消息类型不包含 requestRef
178
+ case MessageType.REGISTER:
179
+ case MessageType.REGISTER_ACK:
180
+ case MessageType.UNREGISTER:
181
+ case MessageType.UNREGISTER_ACK:
182
+ case MessageType.HEARTBEAT:
183
+ case MessageType.HEARTBEAT_ACK:
184
+ return undefined;
185
+
186
+ default:
187
+ return undefined;
188
+ }
189
+ }
190
+
191
+ /**
192
+ * 生成唯一的请求引用 ID
193
+ * @param prefix 可选的前缀,用于标识请求来源
194
+ * @returns 格式为 "prefix-timestamp-random" 或 "timestamp-random"
195
+ */
196
+ static generateRequestRef(prefix?: string): string {
197
+ const timestamp = Date.now();
198
+ const random = Math.random().toString(36).substr(2, 9);
199
+
200
+ if (prefix) {
201
+ // 清理前缀,只保留字母数字和连字符
202
+ const cleanPrefix = prefix.replace(/[^a-zA-Z0-9-]/g, '');
203
+ return `${cleanPrefix}-${timestamp}-${random}`;
204
+ }
205
+
206
+ return `${timestamp}-${random}`;
207
+ }
208
+
209
+ /**
210
+ * 生成带有特定格式的请求引用
211
+ * @param service 服务名称
212
+ * @param operation 操作名称
213
+ * @returns 格式为 "service:operation:timestamp:random"
214
+ */
215
+ static generateStructuredRequestRef(service: string, operation: string): string {
216
+ const timestamp = Date.now();
217
+ const random = Math.random().toString(36).substr(2, 6);
218
+ const cleanService = service.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
219
+ const cleanOperation = operation.replace(/[^a-zA-Z0-9]/g, '').toLowerCase();
220
+
221
+ return `${cleanService}:${cleanOperation}:${timestamp}:${random}`;
222
+ }
223
+
224
+ /**
225
+ * 安全的枚举转换(不抛出异常)
226
+ * @returns 枚举值或 null
227
+ */
228
+ static tryParseStatus(status: string): CommandStatus | null {
229
+ try {
230
+ return this.stringToStatus(status);
231
+ } catch {
232
+ return null;
233
+ }
234
+ }
235
+
236
+ /**
237
+ * 安全的优先级转换(不抛出异常)
238
+ * @returns 枚举值或 null
239
+ */
240
+ static tryParsePriority(priority: string): Priority | null {
241
+ try {
242
+ return this.priorityToEnum(priority);
243
+ } catch {
244
+ return null;
245
+ }
246
+ }
247
+
248
+ /**
249
+ * 安全的客户端类型转换(不抛出异常)
250
+ * @returns 枚举值或 null
251
+ */
252
+ static tryParseClientType(clientType: string): ClientType | null {
253
+ try {
254
+ return this.clientTypeToEnum(clientType);
255
+ } catch {
256
+ return null;
257
+ }
258
+ }
259
+
260
+ /**
261
+ * 从请求引用中解析信息
262
+ * @returns 解析后的组件,如果格式不匹配则返回 null
263
+ */
264
+ static parseRequestRef(requestRef: string): {
265
+ prefix?: string;
266
+ timestamp?: number;
267
+ random?: string;
268
+ service?: string;
269
+ operation?: string;
270
+ } | null {
271
+ // 尝试解析结构化格式 "service:operation:timestamp:random"
272
+ const structuredMatch = requestRef.match(/^([a-z]+):([a-z]+):(\d+):([a-z0-9]+)$/);
273
+ if (structuredMatch) {
274
+ return {
275
+ service: structuredMatch[1],
276
+ operation: structuredMatch[2],
277
+ timestamp: parseInt(structuredMatch[3], 10),
278
+ random: structuredMatch[4]
279
+ };
280
+ }
281
+
282
+ // 尝试解析带前缀格式 "prefix-timestamp-random"
283
+ const prefixMatch = requestRef.match(/^([a-zA-Z0-9-]+)-(\d+)-([a-z0-9]+)$/);
284
+ if (prefixMatch) {
285
+ return {
286
+ prefix: prefixMatch[1],
287
+ timestamp: parseInt(prefixMatch[2], 10),
288
+ random: prefixMatch[3]
289
+ };
290
+ }
291
+
292
+ // 尝试解析简单格式 "timestamp-random"
293
+ const simpleMatch = requestRef.match(/^(\d+)-([a-z0-9]+)$/);
294
+ if (simpleMatch) {
295
+ return {
296
+ timestamp: parseInt(simpleMatch[1], 10),
297
+ random: simpleMatch[2]
298
+ };
299
+ }
300
+
301
+ return null;
302
+ }
303
+
304
+ /**
305
+ * 获取消息的类型名称(友好的字符串格式)
306
+ */
307
+ static getMessageTypeName(message: AnyMessage): string {
308
+ const typeNames: Record<MessageType, string> = {
309
+ [MessageType.REGISTER]: 'Register',
310
+ [MessageType.REGISTER_ACK]: 'Register Acknowledgment',
311
+ [MessageType.UNREGISTER]: 'Unregister',
312
+ [MessageType.UNREGISTER_ACK]: 'Unregister Acknowledgment',
313
+ [MessageType.HEARTBEAT]: 'Heartbeat',
314
+ [MessageType.HEARTBEAT_ACK]: 'Heartbeat Acknowledgment',
315
+ [MessageType.COMMAND]: 'Command',
316
+ [MessageType.COMMAND_RESPONSE]: 'Command Response',
317
+ [MessageType.PROGRAM]: 'Program Upload',
318
+ [MessageType.PROGRAM_RESPONSE]: 'Program Response',
319
+ [MessageType.PROGRESS_UPDATE]: 'Progress Update',
320
+ [MessageType.ERROR]: 'Error'
321
+ };
322
+
323
+ return typeNames[message.type] || 'Unknown';
324
+ }
325
+
326
+ /**
327
+ * 检查是否为请求消息(需要响应的消息)
328
+ */
329
+ static isRequestMessage(message: AnyMessage): boolean {
330
+ const requestTypes: MessageType[] = [
331
+ MessageType.REGISTER,
332
+ MessageType.UNREGISTER,
333
+ MessageType.HEARTBEAT,
334
+ MessageType.COMMAND,
335
+ MessageType.PROGRAM
336
+ ];
337
+
338
+ return requestTypes.includes(message.type);
339
+ }
340
+
341
+ /**
342
+ * 检查是否为响应消息
343
+ */
344
+ static isResponseMessage(message: AnyMessage): boolean {
345
+ const responseTypes: MessageType[] = [
346
+ MessageType.REGISTER_ACK,
347
+ MessageType.UNREGISTER_ACK,
348
+ MessageType.HEARTBEAT_ACK,
349
+ MessageType.COMMAND_RESPONSE,
350
+ MessageType.PROGRAM_RESPONSE
351
+ ];
352
+
353
+ return responseTypes.includes(message.type);
354
+ }
355
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "declaration": true,
13
+ "declarationMap": true,
14
+ "sourceMap": true
15
+ },
16
+ "include": ["src/**/*"],
17
+ "exclude": [
18
+ "node_modules",
19
+ "dist",
20
+ "src/__tests__/**/*",
21
+ "src/edge-proxy.ts",
22
+ "src/gateway-extensions.ts"
23
+ ]
24
+ }