@tx5dr/contracts 2.0.0 → 2.5.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 (138) hide show
  1. package/dist/index.d.ts +4 -1
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +3 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/schema/__tests__/assisted-queue.schema.test.js +1 -0
  6. package/dist/schema/__tests__/assisted-queue.schema.test.js.map +1 -1
  7. package/dist/schema/__tests__/meter.schema.test.d.ts +2 -0
  8. package/dist/schema/__tests__/meter.schema.test.d.ts.map +1 -0
  9. package/dist/schema/__tests__/meter.schema.test.js +27 -0
  10. package/dist/schema/__tests__/meter.schema.test.js.map +1 -0
  11. package/dist/schema/__tests__/operator-context-report.schema.test.d.ts +2 -0
  12. package/dist/schema/__tests__/operator-context-report.schema.test.d.ts.map +1 -0
  13. package/dist/schema/__tests__/operator-context-report.schema.test.js +70 -0
  14. package/dist/schema/__tests__/operator-context-report.schema.test.js.map +1 -0
  15. package/dist/schema/__tests__/plugin-market.schema.test.js +107 -3
  16. package/dist/schema/__tests__/plugin-market.schema.test.js.map +1 -1
  17. package/dist/schema/__tests__/qso.schema.test.d.ts +2 -0
  18. package/dist/schema/__tests__/qso.schema.test.d.ts.map +1 -0
  19. package/dist/schema/__tests__/qso.schema.test.js +70 -0
  20. package/dist/schema/__tests__/qso.schema.test.js.map +1 -0
  21. package/dist/schema/__tests__/radio-power-support.test.js +4 -1
  22. package/dist/schema/__tests__/radio-power-support.test.js.map +1 -1
  23. package/dist/schema/__tests__/radio.schema.test.js +52 -1
  24. package/dist/schema/__tests__/radio.schema.test.js.map +1 -1
  25. package/dist/schema/__tests__/remote-access.schema.test.js +1 -1
  26. package/dist/schema/__tests__/remote-access.schema.test.js.map +1 -1
  27. package/dist/schema/__tests__/spectrum.schema.test.d.ts +2 -0
  28. package/dist/schema/__tests__/spectrum.schema.test.d.ts.map +1 -0
  29. package/dist/schema/__tests__/spectrum.schema.test.js +107 -0
  30. package/dist/schema/__tests__/spectrum.schema.test.js.map +1 -0
  31. package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts +2 -0
  32. package/dist/schema/__tests__/strategy-transmission.schema.test.d.ts.map +1 -0
  33. package/dist/schema/__tests__/strategy-transmission.schema.test.js +95 -0
  34. package/dist/schema/__tests__/strategy-transmission.schema.test.js.map +1 -0
  35. package/dist/schema/api-response.schema.d.ts +235 -0
  36. package/dist/schema/api-response.schema.d.ts.map +1 -1
  37. package/dist/schema/api-response.schema.js +9 -0
  38. package/dist/schema/api-response.schema.js.map +1 -1
  39. package/dist/schema/auth.schema.d.ts +2 -2
  40. package/dist/schema/cpu-profile.schema.d.ts +2 -2
  41. package/dist/schema/device-ui.schema.d.ts +10 -10
  42. package/dist/schema/image-radio.schema.d.ts +2597 -0
  43. package/dist/schema/image-radio.schema.d.ts.map +1 -0
  44. package/dist/schema/image-radio.schema.js +283 -0
  45. package/dist/schema/image-radio.schema.js.map +1 -0
  46. package/dist/schema/logbook.schema.d.ts +203 -15
  47. package/dist/schema/logbook.schema.d.ts.map +1 -1
  48. package/dist/schema/logbook.schema.js +1 -0
  49. package/dist/schema/logbook.schema.js.map +1 -1
  50. package/dist/schema/mode.schema.d.ts +2 -0
  51. package/dist/schema/mode.schema.d.ts.map +1 -1
  52. package/dist/schema/mode.schema.js +16 -0
  53. package/dist/schema/mode.schema.js.map +1 -1
  54. package/dist/schema/openwebrx.schema.d.ts +2 -2
  55. package/dist/schema/operator.schema.d.ts +2676 -0
  56. package/dist/schema/operator.schema.d.ts.map +1 -1
  57. package/dist/schema/operator.schema.js +4 -19
  58. package/dist/schema/operator.schema.js.map +1 -1
  59. package/dist/schema/plugin.schema.d.ts +1780 -475
  60. package/dist/schema/plugin.schema.d.ts.map +1 -1
  61. package/dist/schema/plugin.schema.js +108 -12
  62. package/dist/schema/plugin.schema.js.map +1 -1
  63. package/dist/schema/qso.schema.d.ts +239 -1
  64. package/dist/schema/qso.schema.d.ts.map +1 -1
  65. package/dist/schema/qso.schema.js +61 -0
  66. package/dist/schema/qso.schema.js.map +1 -1
  67. package/dist/schema/radio-capability.schema.d.ts +1 -1
  68. package/dist/schema/radio-capability.schema.d.ts.map +1 -1
  69. package/dist/schema/radio-capability.schema.js +1 -0
  70. package/dist/schema/radio-capability.schema.js.map +1 -1
  71. package/dist/schema/radio-power.schema.d.ts +15 -0
  72. package/dist/schema/radio-power.schema.d.ts.map +1 -1
  73. package/dist/schema/radio-profile.schema.d.ts +251 -0
  74. package/dist/schema/radio-profile.schema.d.ts.map +1 -1
  75. package/dist/schema/radio-profile.schema.js +7 -0
  76. package/dist/schema/radio-profile.schema.js.map +1 -1
  77. package/dist/schema/radio.schema.d.ts +174 -3
  78. package/dist/schema/radio.schema.d.ts.map +1 -1
  79. package/dist/schema/radio.schema.js +31 -2
  80. package/dist/schema/radio.schema.js.map +1 -1
  81. package/dist/schema/rigctld.schema.d.ts +6 -6
  82. package/dist/schema/slot-info.schema.d.ts +13 -0
  83. package/dist/schema/slot-info.schema.d.ts.map +1 -1
  84. package/dist/schema/slot-info.schema.js +2 -1
  85. package/dist/schema/slot-info.schema.js.map +1 -1
  86. package/dist/schema/spectrum-config.schema.d.ts +638 -0
  87. package/dist/schema/spectrum-config.schema.d.ts.map +1 -0
  88. package/dist/schema/spectrum-config.schema.js +129 -0
  89. package/dist/schema/spectrum-config.schema.js.map +1 -0
  90. package/dist/schema/spectrum.schema.d.ts +1313 -20
  91. package/dist/schema/spectrum.schema.d.ts.map +1 -1
  92. package/dist/schema/spectrum.schema.js +72 -0
  93. package/dist/schema/spectrum.schema.js.map +1 -1
  94. package/dist/schema/transmission.schema.d.ts +7 -0
  95. package/dist/schema/transmission.schema.d.ts.map +1 -1
  96. package/dist/schema/tx-audio-input.schema.d.ts +8 -0
  97. package/dist/schema/tx-audio-input.schema.d.ts.map +1 -0
  98. package/dist/schema/tx-audio-input.schema.js +19 -0
  99. package/dist/schema/tx-audio-input.schema.js.map +1 -0
  100. package/dist/schema/voice.schema.d.ts +1 -1
  101. package/dist/schema/voice.schema.d.ts.map +1 -1
  102. package/dist/schema/voice.schema.js +1 -1
  103. package/dist/schema/voice.schema.js.map +1 -1
  104. package/dist/schema/websocket.schema.d.ts +27480 -2525
  105. package/dist/schema/websocket.schema.d.ts.map +1 -1
  106. package/dist/schema/websocket.schema.js +298 -11
  107. package/dist/schema/websocket.schema.js.map +1 -1
  108. package/package.json +1 -1
  109. package/src/index.ts +4 -0
  110. package/src/schema/__tests__/assisted-queue.schema.test.ts +1 -0
  111. package/src/schema/__tests__/meter.schema.test.ts +28 -0
  112. package/src/schema/__tests__/operator-context-report.schema.test.ts +77 -0
  113. package/src/schema/__tests__/plugin-market.schema.test.ts +116 -2
  114. package/src/schema/__tests__/qso.schema.test.ts +91 -0
  115. package/src/schema/__tests__/radio-power-support.test.ts +4 -1
  116. package/src/schema/__tests__/radio.schema.test.ts +60 -1
  117. package/src/schema/__tests__/remote-access.schema.test.ts +1 -1
  118. package/src/schema/__tests__/spectrum.schema.test.ts +119 -0
  119. package/src/schema/__tests__/strategy-transmission.schema.test.ts +106 -0
  120. package/src/schema/api-response.schema.ts +11 -0
  121. package/src/schema/image-radio.schema.ts +342 -0
  122. package/src/schema/logbook.schema.ts +1 -0
  123. package/src/schema/mode.schema.ts +16 -0
  124. package/src/schema/operator.schema.ts +4 -19
  125. package/src/schema/plugin.schema.ts +119 -13
  126. package/src/schema/qso.schema.ts +71 -0
  127. package/src/schema/radio-capability.schema.ts +1 -0
  128. package/src/schema/radio-profile.schema.ts +7 -0
  129. package/src/schema/radio.schema.ts +35 -2
  130. package/src/schema/slot-info.schema.ts +2 -1
  131. package/src/schema/spectrum-config.schema.ts +156 -0
  132. package/src/schema/spectrum.schema.ts +89 -0
  133. package/src/schema/tx-audio-input.schema.ts +22 -0
  134. package/src/schema/voice.schema.ts +1 -1
  135. package/src/schema/websocket.schema.ts +383 -12
  136. package/test/image-radio-schema.test.ts +107 -0
  137. package/test/plugin-slot-schema.test.ts +31 -0
  138. package/CLAUDE.md +0 -41
@@ -0,0 +1,107 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ EngineModeSchema,
5
+ ImageRadioCapabilitySchema,
6
+ ImageHistoryRecordSchema,
7
+ ImageComposerBackgroundSchema,
8
+ ImagePaperBoundarySchema,
9
+ ImageFaxCalibrationSchema,
10
+ FaxCalibrationSetCommandSchema,
11
+ ImageReceiveProfileSchema,
12
+ ImageTemplateSchema,
13
+ SstvTxStartCommandSchema,
14
+ } from '../src/index.js';
15
+
16
+ describe('image radio contracts', () => {
17
+ it('adds image as an engine mode', () => {
18
+ expect(EngineModeSchema.parse('image')).toBe('image');
19
+ });
20
+
21
+ it('keeps fax receive-only at the schema boundary', () => {
22
+ const base = { available: true, sstv: { rx: true, tx: true }, fax: { rx: true, tx: false } };
23
+ expect(ImageRadioCapabilitySchema.parse(base).fax.tx).toBe(false);
24
+ expect(() => ImageRadioCapabilitySchema.parse({ ...base, fax: { rx: true, tx: true } })).toThrow();
25
+ });
26
+
27
+ it('separates automatic SSTV from fixed immediate receive profiles', () => {
28
+ expect(ImageReceiveProfileSchema.parse({ family: 'sstv', strategy: 'auto' })).toEqual({ family: 'sstv', strategy: 'auto' });
29
+ expect(ImageReceiveProfileSchema.parse({ family: 'sstv', strategy: 'manual', mode: 'robot36' }).mode).toBe('robot36');
30
+ expect(() => ImageReceiveProfileSchema.parse({ family: 'sstv', strategy: 'manual' })).toThrow();
31
+ expect(ImageReceiveProfileSchema.parse({ family: 'fax', strategy: 'auto' })).toEqual({ family: 'fax', strategy: 'auto' });
32
+ expect(ImageReceiveProfileSchema.parse({
33
+ family: 'fax', strategy: 'manual', ioc: 'ioc576', lpm: 120,
34
+ modulation: 'am', centerHz: 1900, deviationHz: 400,
35
+ }).family).toBe('fax');
36
+ });
37
+
38
+ it('bounds templates and validates idempotent TX requests', () => {
39
+ const layer = { id: 'line', text: '{MYCALL}', x: 0, y: 0, width: 1, height: 0.2, fontSize: 0.1, color: '#ffffff', align: 'center' };
40
+ const parsedTemplate = ImageTemplateSchema.parse({ id: 'legacy', name: 'Legacy', layers: [layer], createdAt: 1, updatedAt: 1 });
41
+ expect(parsedTemplate.layers[0]).toMatchObject({ align: 'center', rotation: 0, strokeWidth: 0.12 });
42
+ expect(ImageTemplateSchema.parse({ ...parsedTemplate, layers: [{ ...parsedTemplate.layers[0], rotation: 45 }] }).layers[0].rotation).toBe(45);
43
+ expect(ImageTemplateSchema.parse({ ...parsedTemplate, layers: [{ ...parsedTemplate.layers[0], x: -0.5, width: 2 }] }).layers[0]).toMatchObject({ x: -0.5, width: 2 });
44
+ expect(() => ImageTemplateSchema.parse({ ...parsedTemplate, layers: [{ ...parsedTemplate.layers[0], rotation: 181 }] })).toThrow();
45
+ expect(() => ImageTemplateSchema.parse({ ...parsedTemplate, layers: [{ ...parsedTemplate.layers[0], strokeWidth: 0.51 }] })).toThrow();
46
+ expect(() => ImageTemplateSchema.parse({ ...parsedTemplate, layers: [{ ...parsedTemplate.layers[0], x: -2.1 }] })).toThrow();
47
+ expect(() => ImageTemplateSchema.parse({ id: 't', name: 'T', layers: Array.from({ length: 17 }, (_, index) => ({ ...layer, id: String(index) })), createdAt: 1, updatedAt: 1 })).toThrow();
48
+ expect(SstvTxStartCommandSchema.parse({ requestId: 'request-1', operatorId: 'op', artifactId: 'a', mode: 'robot36', expectedFrequency: 14_230_000 })).toMatchObject({
49
+ requestId: 'request-1', envelope: { enhancedPreamble: true, stationIdMode: 'fsk' },
50
+ });
51
+ expect(SstvTxStartCommandSchema.parse({ requestId: 'request-2', operatorId: 'op', artifactId: 'a', mode: 'robot36', expectedFrequency: 14_230_000, interruptActiveCapture: true }).interruptActiveCapture).toBe(true);
52
+ expect(SstvTxStartCommandSchema.parse({
53
+ requestId: 'request-3', operatorId: 'op', artifactId: 'a', mode: 'robot36', expectedFrequency: 14_230_000,
54
+ envelope: { enhancedPreamble: true, stationIdMode: 'cw' },
55
+ }).envelope).toEqual({ enhancedPreamble: true, stationIdMode: 'cw' });
56
+ });
57
+
58
+ it('separates received captures from real transmit history', () => {
59
+ expect(ImageHistoryRecordSchema.parse({
60
+ id: 'rx', artifactId: 'image', family: 'sstv', direction: 'rx', occurredAt: 1,
61
+ saveReason: 'manual', complete: false, truncated: false,
62
+ }).direction).toBe('rx');
63
+ expect(ImageHistoryRecordSchema.parse({
64
+ id: 'tx', artifactId: 'image', family: 'sstv', direction: 'tx', operatorId: 'op',
65
+ sessionId: 'session', occurredAt: 1, startedAt: 1, outcome: 'completed',
66
+ }).direction).toBe('tx');
67
+ expect(() => ImageHistoryRecordSchema.parse({
68
+ id: 'tx', artifactId: 'image', family: 'sstv', direction: 'tx',
69
+ sessionId: 'session', occurredAt: 1, startedAt: 1, outcome: 'completed',
70
+ })).toThrow();
71
+ });
72
+
73
+ it('distinguishes local transmit paper segments from received content', () => {
74
+ const boundary = ImagePaperBoundarySchema.parse({
75
+ boundaryId: 'tx:start', lineIndex: 12, kind: 'localTxStart', trusted: false,
76
+ codecMode: 'robot36', width: 320, pixelFormat: 'rgb8', timestamp: 1,
77
+ source: 'localTx', txSessionId: 'tx-1',
78
+ });
79
+ expect(boundary).toMatchObject({ kind: 'localTxStart', source: 'localTx', txSessionId: 'tx-1' });
80
+ });
81
+
82
+ it('validates per-operator composer background metadata', () => {
83
+ expect(ImageComposerBackgroundSchema.parse({
84
+ operatorId: 'op', width: 1024, height: 512, updatedAt: 1, imageUrl: '/background.png',
85
+ }).operatorId).toBe('op');
86
+ expect(() => ImageComposerBackgroundSchema.parse({
87
+ operatorId: '', width: 1024, height: 512, updatedAt: 1, imageUrl: '/background.png',
88
+ })).toThrow();
89
+ });
90
+
91
+ it('bounds FAX calibration commands and control points', () => {
92
+ const calibration = ImageFaxCalibrationSchema.parse({
93
+ boundaryId: 'fax:1', revision: 2, autoEnabled: true,
94
+ autoPoints: [{ revision: 1, referenceLine: 10, phasePixels: 2, clockPpm: 25, confidence: 0.8, source: 'phasing', status: 'locked' }],
95
+ manualPhasePixels: 0, manualClockPpm: 0, updatedAt: 1,
96
+ });
97
+ expect(calibration.autoPoints[0].clockPpm).toBe(25);
98
+ expect(FaxCalibrationSetCommandSchema.parse({
99
+ requestId: 'r', operatorId: 'op', sessionId: 's', boundaryId: 'fax:1', expectedRevision: 2,
100
+ autoEnabled: true, phasePixels: 10, clockPpm: 5000,
101
+ }).clockPpm).toBe(5000);
102
+ expect(() => FaxCalibrationSetCommandSchema.parse({
103
+ requestId: 'r', operatorId: 'op', sessionId: 's', boundaryId: 'fax:1', expectedRevision: 2,
104
+ autoEnabled: true, phasePixels: 10, clockPpm: 5001,
105
+ })).toThrow();
106
+ });
107
+ });
@@ -5,6 +5,7 @@ describe('PluginPanelSlotSchema', () => {
5
5
  it('accepts every supported plugin panel slot', () => {
6
6
  expect(PluginPanelSlotSchema.parse('operator')).toBe('operator');
7
7
  expect(PluginPanelSlotSchema.parse('automation')).toBe('automation');
8
+ expect(PluginPanelSlotSchema.parse('operator-action')).toBe('operator-action');
8
9
  expect(PluginPanelSlotSchema.parse('main-right')).toBe('main-right');
9
10
  expect(PluginPanelSlotSchema.parse('voice-left-top')).toBe('voice-left-top');
10
11
  expect(PluginPanelSlotSchema.parse('voice-right-top')).toBe('voice-right-top');
@@ -13,6 +14,27 @@ describe('PluginPanelSlotSchema', () => {
13
14
  expect(PluginPanelSlotSchema.parse('radio-control-toolbar')).toBe('radio-control-toolbar');
14
15
  });
15
16
 
17
+ it('requires operator action entries to open an iframe page', () => {
18
+ expect(PluginPanelDescriptorSchema.parse({
19
+ id: 'contest-log',
20
+ title: 'Contest log',
21
+ component: 'iframe',
22
+ pageId: 'contest-log',
23
+ slot: 'operator-action',
24
+ openMode: 'page',
25
+ icon: 'file-lines',
26
+ })).toMatchObject({ slot: 'operator-action', openMode: 'page' });
27
+
28
+ expect(() => PluginPanelDescriptorSchema.parse({
29
+ id: 'embedded-action',
30
+ title: 'Embedded',
31
+ component: 'iframe',
32
+ pageId: 'embedded',
33
+ slot: 'operator-action',
34
+ openMode: 'modal',
35
+ })).toThrow('operator-action panels must use page openMode');
36
+ });
37
+
16
38
  it('validates iframe panels declared in the new host slots', () => {
17
39
  expect(() => PluginPanelDescriptorSchema.parse({
18
40
  id: 'main-pane',
@@ -56,6 +78,15 @@ describe('PluginPanelSlotSchema', () => {
56
78
  uiSize: 'lg',
57
79
  });
58
80
 
81
+ expect(PluginPanelDescriptorSchema.parse({
82
+ id: 'standalone-log',
83
+ title: 'Log',
84
+ component: 'iframe',
85
+ pageId: 'log',
86
+ slot: 'radio-control-toolbar',
87
+ openMode: 'page',
88
+ })).toMatchObject({ openMode: 'page' });
89
+
59
90
  expect(() => PluginPanelDescriptorSchema.parse({
60
91
  id: 'bad-toolbar',
61
92
  title: 'Bad',
package/CLAUDE.md DELETED
@@ -1,41 +0,0 @@
1
- # CLAUDE.md - Contracts
2
-
3
- Zod Schema 和 TypeScript 类型定义包,为整个 TX-5DR 系统提供数据契约。
4
-
5
- ## 核心文件
6
- - **websocket.schema.ts**: WebSocket 消息类型
7
- - **radio/audio.schema.ts**: 电台和音频控制
8
- - **ft8/operator.schema.ts**: FT8 消息和操作员
9
- - **logbook/qso.schema.ts**: 日志本和联络记录
10
- - **其他**: slot-info, mode, cycle, hello
11
-
12
- ## 开发规范
13
-
14
- ### 新增 Schema
15
- 1. `src/schema/` 创建 `.schema.ts` 文件
16
- 2. 定义 Zod Schema:
17
- ```typescript
18
- export const DataSchema = z.object({
19
- id: z.string(),
20
- value: z.number().optional(),
21
- });
22
- export type Data = z.infer<typeof DataSchema>;
23
- ```
24
- 3. `src/index.ts` 导出
25
- 4. `yarn build` 生成类型
26
-
27
- ### WebSocket 消息
28
- ```typescript
29
- export const WSMessageType = {
30
- COMMAND_NAME: 'command_name', // 客户端→服务端
31
- EVENT_NAME: 'event_name', // 服务端→客户端
32
- } as const;
33
- ```
34
-
35
- ### 设计原则
36
- - 严格验证,新字段用 `.optional()` 保证兼容性
37
- - 复用现有 Schema,避免重复定义
38
- - 使用语义化的命名
39
-
40
- ## 命令
41
- `yarn build` - 构建类型定义