@tita-agent/dsh-data-table 0.0.0-stage → 0.2.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 (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -2
  3. package/cordis.patch.yml +7 -0
  4. package/lib/client.js +16766 -0
  5. package/lib/contract-envelope.d.ts +91 -0
  6. package/lib/contract-envelope.js +106 -0
  7. package/lib/contract-layout.d.ts +130 -0
  8. package/lib/contract-layout.js +138 -0
  9. package/lib/contract-refs.d.ts +39 -0
  10. package/lib/contract-refs.js +9 -0
  11. package/lib/contract-version.d.ts +311 -0
  12. package/lib/contract-version.js +270 -0
  13. package/lib/contract-wire.d.ts +147 -0
  14. package/lib/contract-wire.js +99 -0
  15. package/lib/contracts.d.ts +12 -0
  16. package/lib/contracts.js +12 -0
  17. package/lib/types/audit.d.ts +42 -0
  18. package/lib/types/audit.js +39 -0
  19. package/lib/types/client/ProjectImportMenu.d.ts +30 -0
  20. package/lib/types/client/ProjectImportMenu.js +70 -0
  21. package/lib/types/client/SheetPanel.d.ts +33 -0
  22. package/lib/types/client/SheetPanel.js +1766 -0
  23. package/lib/types/client/SheetToggle.d.ts +18 -0
  24. package/lib/types/client/SheetToggle.js +26 -0
  25. package/lib/types/client/index.d.ts +15 -0
  26. package/lib/types/client/index.js +110 -0
  27. package/lib/types/client/persistence/changes.d.ts +15 -0
  28. package/lib/types/client/persistence/changes.js +51 -0
  29. package/lib/types/client/persistence/ids.d.ts +34 -0
  30. package/lib/types/client/persistence/ids.js +48 -0
  31. package/lib/types/client/persistence/merge.d.ts +35 -0
  32. package/lib/types/client/persistence/merge.js +362 -0
  33. package/lib/types/client/store.d.ts +61 -0
  34. package/lib/types/client/store.js +152 -0
  35. package/lib/types/client/surface-context.d.ts +27 -0
  36. package/lib/types/client/surface-context.js +104 -0
  37. package/lib/types/client/surface-control.d.ts +2 -0
  38. package/lib/types/client/surface-control.js +12 -0
  39. package/lib/types/client/transport/workbookApi.d.ts +159 -0
  40. package/lib/types/client/transport/workbookApi.js +246 -0
  41. package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
  42. package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
  43. package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
  44. package/lib/types/client/univer/WorkbookCanvas.js +87 -0
  45. package/lib/types/client/univer/WorkbookView.d.ts +3 -0
  46. package/lib/types/client/univer/WorkbookView.js +2 -0
  47. package/lib/types/client/univer/loader.d.ts +18 -0
  48. package/lib/types/client/univer/loader.js +81 -0
  49. package/lib/types/client/univer/standalone-entry.d.ts +1 -0
  50. package/lib/types/client/univer/standalone-entry.js +19 -0
  51. package/lib/types/client/versions/VersionChrome.d.ts +33 -0
  52. package/lib/types/client/versions/VersionChrome.js +21 -0
  53. package/lib/types/client/versions/inspect.d.ts +70 -0
  54. package/lib/types/client/versions/inspect.js +88 -0
  55. package/lib/types/client/versions/preview-decoration.d.ts +11 -0
  56. package/lib/types/client/versions/preview-decoration.js +54 -0
  57. package/lib/types/client/versions/records.d.ts +19 -0
  58. package/lib/types/client/versions/records.js +70 -0
  59. package/lib/types/client/workspace-host.d.ts +61 -0
  60. package/lib/types/client/workspace-host.js +124 -0
  61. package/lib/types/collaboration-hub.d.ts +31 -0
  62. package/lib/types/collaboration-hub.js +69 -0
  63. package/lib/types/contract/ids.d.ts +24 -0
  64. package/lib/types/contract/ids.js +46 -0
  65. package/lib/types/contract/mutations.d.ts +25 -0
  66. package/lib/types/contract/mutations.js +782 -0
  67. package/lib/types/contract/operations.d.ts +54 -0
  68. package/lib/types/contract/operations.js +203 -0
  69. package/lib/types/contract/snapshots.d.ts +176 -0
  70. package/lib/types/contract/snapshots.js +351 -0
  71. package/lib/types/contract/univer-api-catalog.d.ts +29 -0
  72. package/lib/types/contract/univer-api-catalog.js +264 -0
  73. package/lib/types/errors.d.ts +8 -0
  74. package/lib/types/errors.js +12 -0
  75. package/lib/types/http/assets-routes.d.ts +3 -0
  76. package/lib/types/http/assets-routes.js +169 -0
  77. package/lib/types/http/collaboration.d.ts +10 -0
  78. package/lib/types/http/collaboration.js +154 -0
  79. package/lib/types/http/history.d.ts +15 -0
  80. package/lib/types/http/history.js +134 -0
  81. package/lib/types/http/project-files.d.ts +79 -0
  82. package/lib/types/http/project-files.js +206 -0
  83. package/lib/types/http/project-scope.d.ts +50 -0
  84. package/lib/types/http/project-scope.js +52 -0
  85. package/lib/types/http/routes.d.ts +98 -0
  86. package/lib/types/http/routes.js +323 -0
  87. package/lib/types/http/standalone.d.ts +9 -0
  88. package/lib/types/http/standalone.js +31 -0
  89. package/lib/types/http/trunk.d.ts +10 -0
  90. package/lib/types/http/trunk.js +322 -0
  91. package/lib/types/http/worktree.d.ts +9 -0
  92. package/lib/types/http/worktree.js +145 -0
  93. package/lib/types/index.d.ts +18 -0
  94. package/lib/types/index.js +156 -0
  95. package/lib/types/invariant.d.ts +15 -0
  96. package/lib/types/invariant.js +15 -0
  97. package/lib/types/request-params.d.ts +21 -0
  98. package/lib/types/request-params.js +76 -0
  99. package/lib/types/store/paths.d.ts +68 -0
  100. package/lib/types/store/paths.js +148 -0
  101. package/lib/types/store/project.d.ts +88 -0
  102. package/lib/types/store/project.js +120 -0
  103. package/lib/types/store/read.d.ts +110 -0
  104. package/lib/types/store/read.js +404 -0
  105. package/lib/types/store/retention.d.ts +27 -0
  106. package/lib/types/store/retention.js +111 -0
  107. package/lib/types/store/write.d.ts +22 -0
  108. package/lib/types/store/write.js +210 -0
  109. package/lib/types/tools/arguments.d.ts +40 -0
  110. package/lib/types/tools/arguments.js +130 -0
  111. package/lib/types/tools/cells.d.ts +50 -0
  112. package/lib/types/tools/cells.js +298 -0
  113. package/lib/types/tools/index.d.ts +2 -0
  114. package/lib/types/tools/index.js +250 -0
  115. package/lib/types/tools/render.d.ts +47 -0
  116. package/lib/types/tools/render.js +290 -0
  117. package/lib/types/tools/univer-api.d.ts +18 -0
  118. package/lib/types/tools/univer-api.js +198 -0
  119. package/lib/types/tools/workbook-catalog.d.ts +3 -0
  120. package/lib/types/tools/workbook-catalog.js +83 -0
  121. package/lib/types/tools/workbook-edit.d.ts +192 -0
  122. package/lib/types/tools/workbook-edit.js +912 -0
  123. package/lib/types/tools/workbook-find.d.ts +11 -0
  124. package/lib/types/tools/workbook-find.js +89 -0
  125. package/lib/types/tools/workbook-history.d.ts +2 -0
  126. package/lib/types/tools/workbook-history.js +59 -0
  127. package/lib/types/tools/workbook-read.d.ts +21 -0
  128. package/lib/types/tools/workbook-read.js +204 -0
  129. package/lib/types/types.d.ts +377 -0
  130. package/lib/types/types.js +34 -0
  131. package/lib/types/univer/apply-operation.d.ts +14 -0
  132. package/lib/types/univer/apply-operation.js +398 -0
  133. package/lib/types/univer/chunk.d.ts +68 -0
  134. package/lib/types/univer/chunk.js +16 -0
  135. package/lib/types/univer/decoration.d.ts +34 -0
  136. package/lib/types/univer/decoration.js +88 -0
  137. package/lib/types/univer/diagnostics.d.ts +31 -0
  138. package/lib/types/univer/diagnostics.js +141 -0
  139. package/lib/types/univer/engine.d.ts +33 -0
  140. package/lib/types/univer/engine.js +338 -0
  141. package/lib/types/univer/entry.d.ts +8 -0
  142. package/lib/types/univer/entry.js +11 -0
  143. package/lib/types/univer/facade.d.ts +107 -0
  144. package/lib/types/univer/facade.js +1 -0
  145. package/lib/types/univer/fonts.d.ts +202 -0
  146. package/lib/types/univer/fonts.js +140 -0
  147. package/lib/types/univer/plugins/collaboration.d.ts +23 -0
  148. package/lib/types/univer/plugins/collaboration.js +47 -0
  149. package/lib/types/univer/plugins/export.d.ts +39 -0
  150. package/lib/types/univer/plugins/export.js +246 -0
  151. package/lib/types/univer/plugins/history.d.ts +24 -0
  152. package/lib/types/univer/plugins/history.js +50 -0
  153. package/lib/types/univer/plugins/version.d.ts +13 -0
  154. package/lib/types/univer/plugins/version.js +13 -0
  155. package/lib/types/version/diff.d.ts +36 -0
  156. package/lib/types/version/diff.js +156 -0
  157. package/lib/types/version/grouping.d.ts +7 -0
  158. package/lib/types/version/grouping.js +94 -0
  159. package/lib/types/version/records.d.ts +17 -0
  160. package/lib/types/version/records.js +102 -0
  161. package/lib/types/version/session.d.ts +36 -0
  162. package/lib/types/version/session.js +89 -0
  163. package/lib/types/version/summary.d.ts +24 -0
  164. package/lib/types/version/summary.js +111 -0
  165. package/lib/types/workbook-manager.d.ts +191 -0
  166. package/lib/types/workbook-manager.js +935 -0
  167. package/lib/types/xlsx-export-worker.d.ts +5 -0
  168. package/lib/types/xlsx-export-worker.js +24 -0
  169. package/lib/types/xlsx-export.d.ts +21 -0
  170. package/lib/types/xlsx-export.js +371 -0
  171. package/lib/types/xlsx-import-worker.d.ts +1 -0
  172. package/lib/types/xlsx-import-worker.js +19 -0
  173. package/lib/univer-client.js +690 -0
  174. package/lib/workbook-page.js +23826 -0
  175. package/lib/xlsx-export-worker.js +104 -0
  176. package/lib/xlsx-import-worker.js +46 -0
  177. package/package.json +106 -3
  178. package/src/audit.ts +58 -0
  179. package/src/client/ProjectImportMenu.tsx +100 -0
  180. package/src/client/SheetPanel.module.css +1578 -0
  181. package/src/client/SheetPanel.tsx +1934 -0
  182. package/src/client/SheetToggle.module.css +73 -0
  183. package/src/client/SheetToggle.tsx +65 -0
  184. package/src/client/contract.d.ts +19 -0
  185. package/src/client/index.ts +131 -0
  186. package/src/client/persistence/changes.ts +58 -0
  187. package/src/client/persistence/ids.ts +60 -0
  188. package/src/client/persistence/merge.ts +392 -0
  189. package/src/client/store.ts +188 -0
  190. package/src/client/surface-context.ts +123 -0
  191. package/src/client/surface-control.ts +13 -0
  192. package/src/client/transport/workbookApi.ts +355 -0
  193. package/src/client/univer/SheetView.module.css +110 -0
  194. package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
  195. package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
  196. package/src/client/univer/WorkbookCanvas.tsx +128 -0
  197. package/src/client/univer/WorkbookView.module.css +30 -0
  198. package/src/client/univer/WorkbookView.tsx +3 -0
  199. package/src/client/univer/loader.ts +90 -0
  200. package/src/client/univer/react-dom-client.d.ts +10 -0
  201. package/src/client/univer/standalone-entry.tsx +19 -0
  202. package/src/client/univer/standalone.css +12 -0
  203. package/src/client/univer/univer-locales.d.ts +89 -0
  204. package/src/client/univer/univer-modules.d.ts +26 -0
  205. package/src/client/versions/VersionChrome.tsx +114 -0
  206. package/src/client/versions/inspect.ts +138 -0
  207. package/src/client/versions/preview-decoration.ts +54 -0
  208. package/src/client/versions/records.ts +78 -0
  209. package/src/client/workspace-host.ts +147 -0
  210. package/src/collaboration-hub.ts +83 -0
  211. package/src/contract/ids.ts +50 -0
  212. package/src/contract/mutations.ts +791 -0
  213. package/src/contract/operations.ts +221 -0
  214. package/src/contract/snapshots.ts +383 -0
  215. package/src/contract/univer-api-catalog.ts +289 -0
  216. package/src/css-modules.d.ts +6 -0
  217. package/src/errors.ts +25 -0
  218. package/src/http/assets-routes.ts +177 -0
  219. package/src/http/collaboration.ts +144 -0
  220. package/src/http/history.ts +151 -0
  221. package/src/http/project-files.ts +227 -0
  222. package/src/http/project-scope.ts +77 -0
  223. package/src/http/routes.ts +364 -0
  224. package/src/http/standalone.ts +32 -0
  225. package/src/http/trunk.ts +322 -0
  226. package/src/http/worktree.ts +119 -0
  227. package/src/index.ts +165 -0
  228. package/src/invariant.ts +17 -0
  229. package/src/request-params.ts +92 -0
  230. package/src/store/paths.ts +165 -0
  231. package/src/store/project.ts +137 -0
  232. package/src/store/read.ts +454 -0
  233. package/src/store/retention.ts +106 -0
  234. package/src/store/write.ts +230 -0
  235. package/src/tools/arguments.ts +128 -0
  236. package/src/tools/cells.ts +298 -0
  237. package/src/tools/index.ts +274 -0
  238. package/src/tools/render.ts +301 -0
  239. package/src/tools/univer-api.ts +243 -0
  240. package/src/tools/workbook-catalog.ts +94 -0
  241. package/src/tools/workbook-edit.ts +987 -0
  242. package/src/tools/workbook-find.ts +80 -0
  243. package/src/tools/workbook-history.ts +63 -0
  244. package/src/tools/workbook-read.ts +210 -0
  245. package/src/types.ts +432 -0
  246. package/src/univer/apply-operation.ts +369 -0
  247. package/src/univer/chunk.ts +77 -0
  248. package/src/univer/decoration.ts +111 -0
  249. package/src/univer/diagnostics.ts +136 -0
  250. package/src/univer/engine.ts +370 -0
  251. package/src/univer/entry.ts +14 -0
  252. package/src/univer/facade.ts +91 -0
  253. package/src/univer/fonts.ts +148 -0
  254. package/src/univer/overrides.css +33 -0
  255. package/src/univer/plugins/collaboration.ts +47 -0
  256. package/src/univer/plugins/export.ts +236 -0
  257. package/src/univer/plugins/history.ts +49 -0
  258. package/src/univer/plugins/version.ts +13 -0
  259. package/src/version/diff.ts +169 -0
  260. package/src/version/grouping.ts +96 -0
  261. package/src/version/records.ts +113 -0
  262. package/src/version/session.ts +90 -0
  263. package/src/version/summary.ts +117 -0
  264. package/src/workbook-manager.ts +1111 -0
  265. package/src/xlsx-export-worker.ts +34 -0
  266. package/src/xlsx-export.ts +373 -0
  267. package/src/xlsx-import-worker.ts +30 -0
@@ -0,0 +1,289 @@
1
+ /**
2
+ * JSON-safe metadata for the DSH adapter over Univer's Sheets Facade.
3
+ *
4
+ * The catalog is deliberately an allow-list. It gives an Agent discoverable
5
+ * method names and argument contracts without exposing arbitrary JavaScript or
6
+ * an Injector instance to the tool protocol. Methods that change workbook
7
+ * state map to the same durable operations used by the typed tools.
8
+ */
9
+
10
+ import type { WorkbookOperationType } from '../types.ts'
11
+
12
+ export type UniverApiMode = 'read' | 'write'
13
+ export type UniverApiAvailability = 'implemented' | 'metadata-only'
14
+
15
+ export interface UniverApiArgument {
16
+ type: 'string' | 'number' | 'boolean' | 'object' | 'array' | 'json'
17
+ required?: boolean
18
+ description: string
19
+ }
20
+
21
+ export interface UniverApiDefinition {
22
+ api: string
23
+ mode: UniverApiMode
24
+ availability: UniverApiAvailability
25
+ description: string
26
+ args: Record<string, UniverApiArgument>
27
+ operation?: WorkbookOperationType
28
+ notes?: string
29
+ }
30
+
31
+ const rangeArgs: Record<string, UniverApiArgument> = {
32
+ sheet: { type: 'string', description: 'sheet id 或名称;也可传 sheetId/sheetName;单 sheet workbook 可省略' },
33
+ range: { type: 'object', required: true, description: '0-based 矩形范围:{ startRow, startColumn, endRow, endColumn };也接受 A1:D4' },
34
+ }
35
+
36
+ /** Common Facade methods exposed by the DSH Host adapter. */
37
+ export const UNIVER_API_CATALOG: readonly UniverApiDefinition[] = [
38
+ {
39
+ api: 'workbook.getSnapshot',
40
+ mode: 'read',
41
+ availability: 'implemented',
42
+ description: '读取当前 workbook 的 Univer snapshot。大表格请优先使用 worksheet.getRangeValues。',
43
+ args: {},
44
+ },
45
+ {
46
+ api: 'workbook.getWorksheets',
47
+ mode: 'read',
48
+ availability: 'implemented',
49
+ description: '返回 workbook 内所有 worksheet 的 id、名称和顺序。',
50
+ args: {},
51
+ },
52
+ {
53
+ api: 'worksheet.getRangeValues',
54
+ mode: 'read',
55
+ availability: 'implemented',
56
+ description: '读取 worksheet 的矩形区域值。',
57
+ args: rangeArgs,
58
+ },
59
+ {
60
+ api: 'worksheet.setValues',
61
+ mode: 'write',
62
+ availability: 'implemented',
63
+ description: '通过 Univer Range.setValues 写入值矩阵;range 可只传起点或省略,Host 会按矩阵尺寸推导。',
64
+ args: { sheet: rangeArgs.sheet, range: { type: 'object', description: '可选 0-based 范围;也可只传 startRow/startColumn,或使用 A1:D4' }, values: { type: 'array', required: true, description: '二维数组,也接受单行一维数组' } },
65
+ operation: 'update_cells',
66
+ },
67
+ {
68
+ api: 'worksheet.clearContent',
69
+ mode: 'write',
70
+ availability: 'implemented',
71
+ description: '清除 worksheet range 的内容。',
72
+ args: rangeArgs,
73
+ operation: 'clear_range',
74
+ },
75
+ {
76
+ api: 'worksheet.setBackgroundColor',
77
+ mode: 'write',
78
+ availability: 'implemented',
79
+ description: '设置 range 背景色。',
80
+ args: { ...rangeArgs, color: { type: 'string', required: true, description: 'CSS 颜色' } },
81
+ operation: 'format_cells',
82
+ },
83
+ {
84
+ api: 'worksheet.setFontColor',
85
+ mode: 'write',
86
+ availability: 'implemented',
87
+ description: '设置 range 字体颜色。',
88
+ args: { ...rangeArgs, color: { type: 'string', required: true, description: 'CSS 颜色' } },
89
+ operation: 'format_cells',
90
+ },
91
+ {
92
+ api: 'worksheet.setFontFamily',
93
+ mode: 'write',
94
+ availability: 'implemented',
95
+ description: '设置 range 字体。',
96
+ args: { ...rangeArgs, family: { type: 'string', required: true, description: '字体 family' } },
97
+ operation: 'format_cells',
98
+ },
99
+ {
100
+ api: 'worksheet.setFontSize',
101
+ mode: 'write',
102
+ availability: 'implemented',
103
+ description: '设置 range 字号。',
104
+ args: { ...rangeArgs, size: { type: 'number', required: true, description: '字号,单位 pt' } },
105
+ operation: 'format_cells',
106
+ },
107
+ {
108
+ api: 'worksheet.setFontWeight',
109
+ mode: 'write',
110
+ availability: 'implemented',
111
+ description: '设置 range 粗体状态。',
112
+ args: { ...rangeArgs, bold: { type: 'boolean', required: true, description: '是否粗体' } },
113
+ operation: 'format_cells',
114
+ },
115
+ {
116
+ api: 'worksheet.setFontStyle',
117
+ mode: 'write',
118
+ availability: 'implemented',
119
+ description: '设置 range 斜体状态。',
120
+ args: { ...rangeArgs, italic: { type: 'boolean', required: true, description: '是否斜体' } },
121
+ operation: 'format_cells',
122
+ },
123
+ {
124
+ api: 'worksheet.setHorizontalAlignment',
125
+ mode: 'write',
126
+ availability: 'implemented',
127
+ description: '设置 range 水平对齐。',
128
+ args: { ...rangeArgs, alignment: { type: 'string', required: true, description: 'left、center 或 normal' } },
129
+ operation: 'format_cells',
130
+ },
131
+ {
132
+ api: 'worksheet.setVerticalAlignment',
133
+ mode: 'write',
134
+ availability: 'implemented',
135
+ description: '设置 range 垂直对齐。',
136
+ args: { ...rangeArgs, alignment: { type: 'string', required: true, description: 'top、middle 或 bottom' } },
137
+ operation: 'format_cells',
138
+ },
139
+ {
140
+ api: 'worksheet.merge',
141
+ mode: 'write',
142
+ availability: 'implemented',
143
+ description: '合并 worksheet range。',
144
+ args: rangeArgs,
145
+ operation: 'merge_cells',
146
+ },
147
+ {
148
+ api: 'worksheet.breakApart',
149
+ mode: 'write',
150
+ availability: 'implemented',
151
+ description: '取消 worksheet range 的合并。',
152
+ args: rangeArgs,
153
+ operation: 'unmerge_cells',
154
+ },
155
+ {
156
+ api: 'worksheet.sort',
157
+ mode: 'write',
158
+ availability: 'implemented',
159
+ description: '对 worksheet range 按列排序。',
160
+ args: { ...rangeArgs, column: { type: 'number', required: true, description: '0-based 排序列索引' }, direction: { type: 'string', description: 'asc 或 desc,默认 asc' } },
161
+ operation: 'sort_range',
162
+ },
163
+ {
164
+ api: 'worksheet.setColumnWidth',
165
+ mode: 'write',
166
+ availability: 'implemented',
167
+ description: '设置列宽。',
168
+ args: { sheet: rangeArgs.sheet, index: { type: 'number', required: true, description: '0-based 列索引' }, size: { type: 'number', required: true, description: '像素宽度' } },
169
+ operation: 'set_column_width',
170
+ },
171
+ {
172
+ api: 'worksheet.setRowHeight',
173
+ mode: 'write',
174
+ availability: 'implemented',
175
+ description: '设置行高。',
176
+ args: { sheet: rangeArgs.sheet, index: { type: 'number', required: true, description: '0-based 行索引' }, size: { type: 'number', required: true, description: '像素高度' } },
177
+ operation: 'set_row_height',
178
+ },
179
+ {
180
+ api: 'worksheet.setHyperLink',
181
+ mode: 'write',
182
+ availability: 'implemented',
183
+ description: '给 range 左上角单元格设置超链接;等价于 workbook_edit 的 set_hyperlink command。',
184
+ args: { ...rangeArgs, url: { type: 'string', required: true, description: '目标 URL' }, label: { type: 'string', description: '显示文本' } },
185
+ operation: 'set_hyperlink',
186
+ },
187
+ {
188
+ api: 'worksheet.cancelHyperLink',
189
+ mode: 'write',
190
+ availability: 'implemented',
191
+ description: '移除 range 左上角单元格的超链接。',
192
+ args: rangeArgs,
193
+ operation: 'remove_hyperlink',
194
+ },
195
+ {
196
+ api: 'worksheet.setFreeze',
197
+ mode: 'write',
198
+ availability: 'implemented',
199
+ description: '冻结指定行列。',
200
+ args: { sheet: rangeArgs.sheet, rows: { type: 'number', description: '冻结行数,默认 0' }, columns: { type: 'number', description: '冻结列数,默认 0' } },
201
+ operation: 'freeze_panes',
202
+ },
203
+ {
204
+ api: 'worksheet.deleteCells',
205
+ mode: 'write',
206
+ availability: 'implemented',
207
+ description: '删除 range 内容,或按 left/up 移动邻近单元格。',
208
+ args: { ...rangeArgs, shift: { type: 'string', description: 'clear、left 或 up,默认 clear' } },
209
+ operation: 'delete_cells',
210
+ },
211
+ {
212
+ api: 'worksheet.insertRows',
213
+ mode: 'write',
214
+ availability: 'implemented',
215
+ description: '在指定行位置插入行。',
216
+ args: { sheet: rangeArgs.sheet, index: { type: 'number', required: true, description: '0-based 行位置' }, count: { type: 'number', description: '行数,默认 1' } },
217
+ operation: 'insert_rows',
218
+ },
219
+ {
220
+ api: 'worksheet.deleteRows',
221
+ mode: 'write',
222
+ availability: 'implemented',
223
+ description: '删除指定行。',
224
+ args: { sheet: rangeArgs.sheet, index: { type: 'number', required: true, description: '0-based 行位置' }, count: { type: 'number', description: '行数,默认 1' } },
225
+ operation: 'delete_rows',
226
+ },
227
+ {
228
+ api: 'worksheet.insertColumns',
229
+ mode: 'write',
230
+ availability: 'implemented',
231
+ description: '在指定列位置插入列。',
232
+ args: { sheet: rangeArgs.sheet, index: { type: 'number', required: true, description: '0-based 列位置' }, count: { type: 'number', description: '列数,默认 1' } },
233
+ operation: 'insert_columns',
234
+ },
235
+ {
236
+ api: 'worksheet.deleteColumns',
237
+ mode: 'write',
238
+ availability: 'implemented',
239
+ description: '删除指定列。',
240
+ args: { sheet: rangeArgs.sheet, index: { type: 'number', required: true, description: '0-based 列位置' }, count: { type: 'number', description: '列数,默认 1' } },
241
+ operation: 'delete_columns',
242
+ },
243
+ {
244
+ api: 'worksheet.setTextRotation',
245
+ mode: 'write',
246
+ availability: 'implemented',
247
+ description: '设置 range 文字旋转角度。',
248
+ args: { ...rangeArgs, rotation: { type: 'number', required: true, description: '旋转角度' } },
249
+ operation: 'format_cells',
250
+ },
251
+ {
252
+ api: 'worksheet.setNumberFormat',
253
+ mode: 'write',
254
+ availability: 'implemented',
255
+ description: '设置 range 数字格式。',
256
+ args: { ...rangeArgs, format: { type: 'string', required: true, description: 'Univer number format' } },
257
+ operation: 'format_cells',
258
+ },
259
+ {
260
+ api: 'worksheet.setName',
261
+ mode: 'write',
262
+ availability: 'implemented',
263
+ description: '修改 worksheet 名称。',
264
+ args: { sheet: rangeArgs.sheet, name: { type: 'string', required: true, description: '新的 sheet 名称' } },
265
+ operation: 'rename_sheet',
266
+ },
267
+ {
268
+ api: 'workbook.createSheet',
269
+ mode: 'write',
270
+ availability: 'implemented',
271
+ description: '持久化创建 worksheet;可用 sheetId 指定稳定 id。',
272
+ args: { name: { type: 'string', required: true, description: 'sheet 名称' }, sheetId: { type: 'string', description: '可选稳定 sheet id' } },
273
+ operation: 'create_sheet',
274
+ },
275
+ {
276
+ api: 'workbook.deleteSheet',
277
+ mode: 'write',
278
+ availability: 'implemented',
279
+ description: '持久化删除 worksheet;工作簿至少保留一个 sheet。',
280
+ args: { sheet: { type: 'string', description: 'sheet id 或名称;也接受 sheetId/sheetName' } },
281
+ operation: 'delete_sheet',
282
+ },
283
+ ]
284
+
285
+ export const UNIVER_API_VERSION = '0.25.1-dsh-1'
286
+
287
+ export function findUniverApi(api: string): UniverApiDefinition | undefined {
288
+ return UNIVER_API_CATALOG.find(item => item.api === api)
289
+ }
@@ -0,0 +1,6 @@
1
+ declare module '*.module.css' {
2
+ const classes: Record<string, string>
3
+ export default classes
4
+ }
5
+
6
+ declare module '*.css'
package/src/errors.ts ADDED
@@ -0,0 +1,25 @@
1
+ /** Shared wire-level errors for the Host API and browser client. */
2
+ export type DataSheetErrorCode =
3
+ | 'VALIDATION_ERROR'
4
+ | 'WORKBOOK_NOT_FOUND'
5
+ | 'WORKTREE_NOT_FOUND'
6
+ | 'SESSION_SCOPE_DENIED'
7
+ | 'REVISION_CONFLICT'
8
+ | 'REVISION_UNAVAILABLE'
9
+ | 'WORKTREE_STATE_INVALID'
10
+ | 'PAYLOAD_TOO_LARGE'
11
+ | 'PERSISTENCE_FAILED'
12
+ | 'COLLABORATION_LIMIT'
13
+ | 'INTERNAL_ERROR'
14
+
15
+ export class DataSheetError extends Error {
16
+ constructor(
17
+ readonly code: DataSheetErrorCode,
18
+ message: string,
19
+ readonly status = 400,
20
+ readonly details?: Record<string, unknown>,
21
+ ) {
22
+ super(message)
23
+ this.name = 'DataSheetError'
24
+ }
25
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Static plugin assets served on demand (docs/issue/0022 P3).
3
+ *
4
+ * The Univer engine chunk, the two xlsx workers and the standalone workbook
5
+ * page are all classic scripts or worker entry points the browser fetches after
6
+ * boot, so they are served from here rather than inlined into the client bundle.
7
+ * `univer-api.json` is the catalogue the Agent reads through its tool.
8
+ */
9
+ import { createHash } from 'node:crypto'
10
+ import { existsSync } from 'node:fs'
11
+ import { readFile, stat } from 'node:fs/promises'
12
+ import { dirname, join } from 'node:path'
13
+ import { fileURLToPath } from 'node:url'
14
+ import type { LegacyRoute } from './routes.ts'
15
+ import { guardDataSheetRequest } from './routes.ts'
16
+
17
+ /**
18
+ * The plugin directory, found by walking up to the `package.json` that owns
19
+ * this file.
20
+ *
21
+ * It used to be a pair of `new URL('../../lib/...', import.meta.url)` candidates
22
+ * because the path depth differs between `src/` and the emitted `lib/types/`,
23
+ * which meant every file move silently changed what those URLs pointed at. The
24
+ * walk is depth-independent, so moving this module cannot break it.
25
+ */
26
+ function pluginRoot(): string {
27
+ let directory = dirname(fileURLToPath(import.meta.url))
28
+ for (let depth = 0; depth < 8; depth += 1) {
29
+ if (existsSync(join(directory, 'package.json'))) return directory
30
+ const parent = dirname(directory)
31
+ if (parent === directory) break
32
+ directory = parent
33
+ }
34
+ return dirname(fileURLToPath(import.meta.url))
35
+ }
36
+
37
+ /** Absolute path of one build artifact under `lib/`. */
38
+ const artifact = (name: string): string => join(pluginRoot(), 'lib', name)
39
+
40
+ /** One artifact read, plus the identity that read is still valid for. */
41
+ interface ResidentArtifact {
42
+ /** `mtimeMs:size`; a different string means the file was rebuilt. */
43
+ identity: string
44
+ bytes: Buffer
45
+ /** First 12 hex of the content's SHA-256 — the same value the chunk URL carries. */
46
+ hash: string
47
+ }
48
+
49
+ /** Last read per artifact name. The engine chunk is 13MB and changes only on rebuild. */
50
+ const residentArtifacts = new Map<string, ResidentArtifact>()
51
+
52
+ /**
53
+ * Read one `lib/` artifact, reusing the last read while the file is unchanged.
54
+ *
55
+ * The engine chunk used to be re-read from disk on every request, which was the
56
+ * only way to notice a rebuild. The `mtimeMs`/`size` identity notices the same
57
+ * rebuild without the disk read, and it is what lets the route answer the
58
+ * content-hash check below without hashing 13MB per request.
59
+ *
60
+ * @param name - file name under `lib/`.
61
+ * @returns the current bytes and content hash.
62
+ */
63
+ async function readArtifact(name: string): Promise<ResidentArtifact> {
64
+ const path = artifact(name)
65
+ const info = await stat(path)
66
+ const identity = `${info.mtimeMs}:${info.size}`
67
+ const resident = residentArtifacts.get(name)
68
+ if (resident !== undefined && resident.identity === identity) return resident
69
+ const bytes = await readFile(path)
70
+ const next: ResidentArtifact = { identity, bytes, hash: createHash('sha256').update(bytes).digest('hex').slice(0, 12) }
71
+ residentArtifacts.set(name, next)
72
+ return next
73
+ }
74
+
75
+ /** Every static route this plugin serves. */
76
+ export function assetRoutes(): LegacyRoute[] {
77
+ const routes: LegacyRoute[] = []
78
+ routes.push({
79
+ kind: 'exact',
80
+ path: '/data-sheet/univer-client.js',
81
+ handler: async (req, res) => {
82
+ if (!guardDataSheetRequest(req, res)) return
83
+ try {
84
+ const chunk = await readArtifact('univer-client.js')
85
+ // The loader asks for this file with its content hash in `?v=`. An exact
86
+ // match means the bytes cannot have changed, so the browser may keep them
87
+ // for a year and never ask again — which is the point: the chunk is 13MB
88
+ // and the sheet is opened in a fresh page constantly.
89
+ //
90
+ // Anything else — no `?v=` at all (the `/api/data-sheet?path=` transport mode
91
+ // drops the query), or a hash from a build that has since been replaced —
92
+ // is served `no-store`. A stale URL must never be able to pin different
93
+ // bytes, and that is exactly the failure the old unconditional `no-store`
94
+ // was there to prevent.
95
+ const requested = new URL(req.url ?? '/', 'http://x').searchParams.get('v')
96
+ const immutable = requested !== null && requested === chunk.hash
97
+ res.writeHead(200, {
98
+ 'content-type': 'text/javascript; charset=utf-8',
99
+ 'cache-control': immutable ? 'public, max-age=31536000, immutable' : 'no-store, must-revalidate',
100
+ })
101
+ if (req.method === 'HEAD') {
102
+ res.end()
103
+ return
104
+ }
105
+ res.end(chunk.bytes)
106
+ } catch {
107
+ res.writeHead(404)
108
+ res.end()
109
+ }
110
+ },
111
+ })
112
+
113
+ // XLSX/CSV encoding is loaded only when the user exports. Keeping it in a
114
+ // worker prevents SheetJS serialization from blocking the Univer canvas.
115
+ routes.push({
116
+ kind: 'exact',
117
+ path: '/data-sheet/xlsx-export-worker.js',
118
+ handler: async (_req, res) => {
119
+ if (!guardDataSheetRequest(_req, res)) return
120
+ try {
121
+ const body = await readFile(artifact('xlsx-export-worker.js'))
122
+ res.writeHead(200, {
123
+ 'content-type': 'text/javascript; charset=utf-8',
124
+ 'cache-control': 'no-cache',
125
+ })
126
+ res.end(body)
127
+ } catch {
128
+ res.writeHead(404)
129
+ res.end()
130
+ }
131
+ },
132
+ })
133
+
134
+ routes.push({
135
+ kind: 'exact',
136
+ path: '/data-sheet/xlsx-import-worker.js',
137
+ handler: async (_req, res) => {
138
+ if (!guardDataSheetRequest(_req, res)) return
139
+ try {
140
+ const body = await readFile(artifact('xlsx-import-worker.js'))
141
+ res.writeHead(200, {
142
+ 'content-type': 'text/javascript; charset=utf-8',
143
+ 'cache-control': 'no-cache',
144
+ })
145
+ res.end(body)
146
+ } catch {
147
+ res.writeHead(404)
148
+ res.end()
149
+ }
150
+ },
151
+ })
152
+
153
+ // Standalone client bundle for the optional full-browser workbook page.
154
+ routes.push({
155
+ kind: 'exact',
156
+ path: '/data-sheet/workbook-page.js',
157
+ handler: async (_req, res) => {
158
+ if (!guardDataSheetRequest(_req, res)) return
159
+ try {
160
+ const body = await readFile(artifact('workbook-page.js'))
161
+ res.writeHead(200, {
162
+ 'content-type': 'text/javascript; charset=utf-8',
163
+ 'cache-control': 'no-cache',
164
+ })
165
+ res.end(body)
166
+ } catch {
167
+ res.writeHead(404)
168
+ res.end()
169
+ }
170
+ },
171
+ })
172
+
173
+ // Workbook catalog for the browser picker. Workbook content stays behind
174
+ // the per-workbook snapshot endpoint; this route returns names and sheet
175
+ // summaries and accepts the small lifecycle mutations used by the picker.
176
+ return routes
177
+ }
@@ -0,0 +1,144 @@
1
+ /**
2
+ * The live collaboration stream (docs/issue/0022 P3).
3
+ *
4
+ * A server-sent event stream plus the presence beacon. This is a subscription
5
+ * protocol rather than request/response — the response stays open until the
6
+ * client goes away — which is why it is a module of its own.
7
+ */
8
+ import type { IncomingMessage, ServerResponse } from 'node:http'
9
+ import { DataSheetError } from '../errors.ts'
10
+ import { publishWorkbookPresence, subscribeWorkbookCollaboration, subscribeWorkbookPresence } from '../collaboration-hub.ts'
11
+ import { readWorkbook } from '../workbook-manager.ts'
12
+ import { parseRevision, readJsonBody, requireDataSheetSession, writeOperationError, type WorkbookRoute } from './routes.ts'
13
+ import type { WorkbookOperation, WorkbookPresence } from '../types.ts'
14
+
15
+ export async function handleCollaborationRoute(route: WorkbookRoute, req: IncomingMessage, res: ServerResponse, url: URL): Promise<boolean> {
16
+ const id = route.id
17
+
18
+ if (route.kind === 'presence' && req.method === 'POST') {
19
+ const sessionId = requireDataSheetSession(req, res, url)
20
+ if (sessionId === null) return true
21
+ let body: Record<string, unknown>
22
+ try { body = await readJsonBody(req) } catch (error) { writeOperationError(res, error); return true }
23
+ const kind = body.kind
24
+ if (kind !== 'selection' && kind !== 'editing' && kind !== 'active-sheet') {
25
+ writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'presence.kind 必须是 selection、editing 或 active-sheet'))
26
+ return true
27
+ }
28
+ const payload = body.payload
29
+ if (typeof payload !== 'object' || payload === null || Array.isArray(payload)) {
30
+ writeOperationError(res, new DataSheetError('VALIDATION_ERROR', 'presence.payload 必须是对象'))
31
+ return true
32
+ }
33
+ if (JSON.stringify(payload).length > 8192) {
34
+ writeOperationError(res, new DataSheetError('PAYLOAD_TOO_LARGE', 'presence.payload 不能超过 8 KiB', 413))
35
+ return true
36
+ }
37
+ publishWorkbookPresence({ workbookId: id, sessionId, actor: 'user', kind, payload: payload as Record<string, unknown>, timestamp: new Date().toISOString() })
38
+ res.writeHead(202, { 'content-type': 'application/json; charset=utf-8', 'cache-control': 'no-store' })
39
+ res.end(JSON.stringify({ ok: true, workbookId: id }))
40
+ return true
41
+ }
42
+
43
+ if (route.kind === 'collaboration' && req.method === 'GET') {
44
+ const sessionId = requireDataSheetSession(req, res, url)
45
+ if (sessionId === null) return true
46
+ const initialMeta = await readWorkbook(id)
47
+ if (initialMeta === null) {
48
+ res.writeHead(404)
49
+ res.end()
50
+ return true
51
+ }
52
+ const after = parseRevision(url.searchParams.get('after'))
53
+ const operations = (initialMeta.operations ?? []).filter(operation => operation.revision > after)
54
+ const currentRevision = initialMeta.revision ?? 0
55
+ const oldestRevision = operations[0]?.revision ?? currentRevision
56
+ const requiresSnapshot = after < oldestRevision - 1
57
+ const snapshot = requiresSnapshot
58
+ ? initialMeta.snapshot
59
+ : undefined
60
+ const pendingEvents: Array<{ operation: WorkbookOperation }> = []
61
+ const pendingPresence: Array<{ workbookId: string; presence: WorkbookPresence }> = []
62
+ let ready = false
63
+ let closed = false
64
+ const pendingWrites: string[] = []
65
+ const flushWrites = (): void => {
66
+ while (!closed && pendingWrites.length > 0) {
67
+ const frame = pendingWrites.shift() as string
68
+ if (!res.write(frame)) break
69
+ }
70
+ }
71
+ const writeOperationEvent = (operation: WorkbookOperation): void => {
72
+ if (closed) return
73
+ const frame = `id: ${operation.revision}\nevent: operation\ndata: ${JSON.stringify({ workbookId: id, operation })}\n\n`
74
+ if (pendingWrites.length > 0 || !res.write(frame)) {
75
+ if (pendingWrites.length >= 100) {
76
+ res.destroy()
77
+ return
78
+ }
79
+ pendingWrites.push(frame)
80
+ }
81
+ }
82
+ const writePresenceEvent = (presence: WorkbookPresence): void => {
83
+ if (closed || presence.sessionId === sessionId) return
84
+ const frame = `id: ${Date.parse(presence.timestamp) || Date.now()}\nevent: presence\ndata: ${JSON.stringify({ workbookId: id, presence })}\n\n`
85
+ if (pendingWrites.length > 0 || !res.write(frame)) {
86
+ if (pendingWrites.length >= 100) { res.destroy(); return }
87
+ pendingWrites.push(frame)
88
+ }
89
+ }
90
+ let unsubscribe: (() => void) | undefined
91
+ let unsubscribePresence: (() => void) | undefined
92
+ try {
93
+ unsubscribe = subscribeWorkbookCollaboration(id, event => {
94
+ if (closed) return
95
+ if (!ready) {
96
+ pendingEvents.push(event)
97
+ return
98
+ }
99
+ writeOperationEvent(event.operation)
100
+ })
101
+ unsubscribePresence = subscribeWorkbookPresence(id, event => {
102
+ if (closed || event.presence.sessionId === sessionId) return
103
+ if (!ready) pendingPresence.push(event)
104
+ else writePresenceEvent(event.presence)
105
+ })
106
+ } catch (error) {
107
+ res.writeHead(429, { 'content-type': 'application/json; charset=utf-8', 'retry-after': '5' })
108
+ res.end(JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }))
109
+ return true
110
+ }
111
+ let heartbeat: ReturnType<typeof setInterval> | undefined
112
+ res.on('drain', flushWrites)
113
+ res.once('close', () => {
114
+ closed = true
115
+ if (heartbeat !== undefined) clearInterval(heartbeat)
116
+ unsubscribe?.()
117
+ unsubscribePresence?.()
118
+ })
119
+ heartbeat = setInterval(() => {
120
+ if (!closed) res.write(': heartbeat\n\n')
121
+ }, 15000)
122
+ res.writeHead(200, {
123
+ 'content-type': 'text/event-stream; charset=utf-8',
124
+ 'cache-control': 'no-cache, no-transform',
125
+ connection: 'keep-alive',
126
+ })
127
+ res.write(`event: ready\ndata: ${JSON.stringify({
128
+ workbookId: id,
129
+ revision: currentRevision,
130
+ operations: requiresSnapshot ? [] : operations,
131
+ requiresSnapshot,
132
+ ...(snapshot === undefined ? {} : { snapshot }),
133
+ })}\n\n`)
134
+ ready = true
135
+ for (const event of pendingEvents) {
136
+ if (event.operation.revision > currentRevision) writeOperationEvent(event.operation)
137
+ }
138
+ for (const event of pendingPresence) writePresenceEvent(event.presence)
139
+ await new Promise<void>(resolve => res.once('close', resolve))
140
+ return true
141
+ }
142
+
143
+ return false
144
+ }