@beyondwork/docx-react-component 1.0.29 → 1.0.31

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 (383) hide show
  1. package/package.json +65 -96
  2. package/src/README.md +85 -0
  3. package/src/api/README.md +26 -0
  4. package/src/api/public-types.ts +1952 -0
  5. package/src/api/session-state.ts +62 -0
  6. package/src/compare/diff-engine.ts +623 -0
  7. package/src/compare/export-redlines.ts +280 -0
  8. package/src/compare/index.ts +25 -0
  9. package/src/compare/snapshot.ts +97 -0
  10. package/src/component-inventory.md +99 -0
  11. package/src/core/README.md +10 -0
  12. package/src/core/commands/README.md +3 -0
  13. package/{dist/chunk-TJBP2K4T.js → src/core/commands/formatting-commands.ts} +536 -196
  14. package/src/core/commands/image-commands.ts +373 -0
  15. package/src/core/commands/index.ts +1879 -0
  16. package/src/core/commands/list-commands.ts +565 -0
  17. package/src/core/commands/paragraph-layout-commands.ts +339 -0
  18. package/src/core/commands/review-commands.ts +108 -0
  19. package/{dist/core/commands/section-layout-commands.cjs → src/core/commands/section-layout-commands.ts} +340 -137
  20. package/src/core/commands/structural-helpers.ts +309 -0
  21. package/{dist/core/commands/style-commands.cjs → src/core/commands/style-commands.ts} +113 -65
  22. package/src/core/commands/table-structure-commands.ts +854 -0
  23. package/{dist/chunk-UZXBISGO.js → src/core/commands/text-commands.ts} +142 -86
  24. package/src/core/schema/README.md +3 -0
  25. package/src/core/schema/text-schema.ts +516 -0
  26. package/src/core/search/search-text.ts +357 -0
  27. package/src/core/selection/README.md +3 -0
  28. package/src/core/selection/mapping.ts +289 -0
  29. package/src/core/selection/review-anchors.ts +183 -0
  30. package/src/core/state/README.md +3 -0
  31. package/src/core/state/editor-state.ts +892 -0
  32. package/src/core/state/text-transaction.ts +869 -0
  33. package/src/formats/xlsx/io/parse-shared-strings.ts +41 -0
  34. package/src/formats/xlsx/io/parse-sheet.ts +459 -0
  35. package/src/formats/xlsx/io/parse-styles.ts +59 -0
  36. package/src/formats/xlsx/io/parse-workbook.ts +75 -0
  37. package/src/formats/xlsx/io/serialize-shared-strings.ts +72 -0
  38. package/src/formats/xlsx/io/serialize-sheet.ts +333 -0
  39. package/src/formats/xlsx/io/serialize-styles.ts +98 -0
  40. package/src/formats/xlsx/io/serialize-workbook.ts +429 -0
  41. package/src/formats/xlsx/io/xlsx-session.ts +314 -0
  42. package/src/formats/xlsx/model/cell.ts +189 -0
  43. package/src/formats/xlsx/model/sheet.ts +326 -0
  44. package/src/formats/xlsx/model/styles.ts +118 -0
  45. package/src/formats/xlsx/model/workbook.ts +453 -0
  46. package/src/formats/xlsx/runtime/cell-commands.ts +567 -0
  47. package/src/formats/xlsx/runtime/sheet-commands.ts +206 -0
  48. package/src/formats/xlsx/runtime/workbook-runtime.ts +177 -0
  49. package/src/formats/xlsx/runtime/workbook-transaction.ts +822 -0
  50. package/src/index.ts +142 -0
  51. package/src/io/README.md +10 -0
  52. package/src/io/docx-session.ts +3175 -0
  53. package/src/io/export/README.md +3 -0
  54. package/src/io/export/export-session.ts +220 -0
  55. package/src/io/export/minimal-docx.ts +115 -0
  56. package/src/io/export/reattach-preserved-parts.ts +54 -0
  57. package/src/io/export/serialize-comments.ts +947 -0
  58. package/src/io/export/serialize-footnotes.ts +394 -0
  59. package/src/io/export/serialize-headers-footers.ts +368 -0
  60. package/src/io/export/serialize-main-document.ts +1342 -0
  61. package/src/io/export/serialize-numbering.ts +218 -0
  62. package/src/io/export/serialize-revisions.ts +389 -0
  63. package/src/io/export/serialize-runtime-revisions.ts +463 -0
  64. package/src/io/export/serialize-tables.ts +174 -0
  65. package/src/io/export/split-review-boundaries.ts +356 -0
  66. package/src/io/export/split-story-blocks-for-runtime-revisions.ts +252 -0
  67. package/src/io/export/table-properties-xml.ts +318 -0
  68. package/src/io/normalize/README.md +3 -0
  69. package/src/io/normalize/normalize-text.ts +670 -0
  70. package/src/io/ooxml/README.md +3 -0
  71. package/src/io/ooxml/highlight-colors.ts +39 -0
  72. package/src/io/ooxml/numbering-sentinels.ts +44 -0
  73. package/src/io/ooxml/parse-comments.ts +852 -0
  74. package/src/io/ooxml/parse-complex-content.ts +287 -0
  75. package/src/io/ooxml/parse-fields.ts +834 -0
  76. package/src/io/ooxml/parse-footnotes.ts +952 -0
  77. package/src/io/ooxml/parse-headers-footers.ts +1212 -0
  78. package/src/io/ooxml/parse-inline-media.ts +461 -0
  79. package/src/io/ooxml/parse-main-document.ts +2947 -0
  80. package/src/io/ooxml/parse-numbering.ts +747 -0
  81. package/src/io/ooxml/parse-revisions.ts +1045 -0
  82. package/src/io/ooxml/parse-settings.ts +184 -0
  83. package/src/io/ooxml/parse-shapes.ts +296 -0
  84. package/src/io/ooxml/parse-styles.ts +639 -0
  85. package/src/io/ooxml/parse-tables.ts +627 -0
  86. package/src/io/ooxml/parse-theme.ts +346 -0
  87. package/src/io/ooxml/part-manifest.ts +136 -0
  88. package/src/io/ooxml/revision-boundaries.ts +475 -0
  89. package/src/io/ooxml/workflow-payload.ts +544 -0
  90. package/src/io/opc/README.md +3 -0
  91. package/src/io/opc/corrupt-package.ts +166 -0
  92. package/src/io/opc/docx-package.ts +74 -0
  93. package/src/io/opc/package-reader.ts +325 -0
  94. package/src/io/opc/package-writer.ts +273 -0
  95. package/src/io/source-package-provenance.ts +241 -0
  96. package/{dist/chunk-RMH72RZI.js → src/legal/bookmarks.ts} +130 -44
  97. package/src/legal/cross-references.ts +414 -0
  98. package/src/legal/defined-terms.ts +203 -0
  99. package/src/legal/index.ts +32 -0
  100. package/src/legal/signature-blocks.ts +259 -0
  101. package/src/model/README.md +3 -0
  102. package/src/model/canonical-document.ts +2722 -0
  103. package/src/model/cds-1.0.0.ts +212 -0
  104. package/src/model/snapshot.ts +760 -0
  105. package/src/preservation/README.md +3 -0
  106. package/src/preservation/markup-compatibility.ts +48 -0
  107. package/src/preservation/opaque-fragment-store.ts +89 -0
  108. package/src/preservation/opaque-region.ts +233 -0
  109. package/src/preservation/package-preservation.ts +113 -0
  110. package/src/preservation/preserved-part-manifest.ts +56 -0
  111. package/src/preservation/relationship-retention.ts +57 -0
  112. package/src/preservation/store.ts +255 -0
  113. package/src/review/README.md +16 -0
  114. package/src/review/store/README.md +3 -0
  115. package/src/review/store/comment-anchors.ts +70 -0
  116. package/src/review/store/comment-remapping.ts +154 -0
  117. package/src/review/store/comment-store.ts +349 -0
  118. package/src/review/store/comment-thread.ts +109 -0
  119. package/src/review/store/revision-actions.ts +423 -0
  120. package/src/review/store/revision-store.ts +323 -0
  121. package/src/review/store/revision-types.ts +182 -0
  122. package/src/review/store/runtime-comment-store.ts +43 -0
  123. package/src/runtime/README.md +3 -0
  124. package/src/runtime/ai-action-policy.ts +764 -0
  125. package/src/runtime/context-analytics.ts +824 -0
  126. package/src/runtime/document-layout.ts +332 -0
  127. package/src/runtime/document-locations.ts +521 -0
  128. package/src/runtime/document-navigation.ts +616 -0
  129. package/src/runtime/document-outline.ts +440 -0
  130. package/src/runtime/document-runtime.ts +4055 -0
  131. package/src/runtime/document-search.ts +145 -0
  132. package/src/runtime/event-refresh-hints.ts +137 -0
  133. package/src/runtime/numbering-prefix.ts +244 -0
  134. package/src/runtime/page-layout-estimation.ts +305 -0
  135. package/src/runtime/read-only-diagnostics-runtime.ts +241 -0
  136. package/src/runtime/resolved-numbering-geometry.ts +293 -0
  137. package/src/runtime/review-runtime.ts +44 -0
  138. package/src/runtime/revision-runtime.ts +107 -0
  139. package/src/runtime/session-capabilities.ts +192 -0
  140. package/src/runtime/story-context.ts +164 -0
  141. package/src/runtime/story-targeting.ts +162 -0
  142. package/src/runtime/suggestions-snapshot.ts +137 -0
  143. package/src/runtime/surface-projection.ts +1553 -0
  144. package/src/runtime/table-commands.ts +173 -0
  145. package/src/runtime/table-schema.ts +309 -0
  146. package/src/runtime/table-style-resolver.ts +409 -0
  147. package/src/runtime/view-state.ts +493 -0
  148. package/src/runtime/virtualized-rendering.ts +258 -0
  149. package/src/runtime/workflow-markup.ts +393 -0
  150. package/src/ui/README.md +30 -0
  151. package/src/ui/WordReviewEditor.tsx +5268 -0
  152. package/src/ui/browser-export.ts +52 -0
  153. package/src/ui/comments/README.md +3 -0
  154. package/src/ui/compatibility/README.md +3 -0
  155. package/src/ui/editor-command-bag.ts +127 -0
  156. package/src/ui/editor-runtime-boundary.ts +1558 -0
  157. package/src/ui/editor-shell-view.tsx +144 -0
  158. package/src/ui/editor-surface/README.md +3 -0
  159. package/src/ui/editor-surface-controller.tsx +66 -0
  160. package/src/ui/headless/comment-decoration-model.ts +124 -0
  161. package/src/ui/headless/preserve-editor-selection.ts +5 -0
  162. package/src/ui/headless/revision-decoration-model.ts +128 -0
  163. package/src/ui/headless/selection-helpers.ts +54 -0
  164. package/src/ui/headless/selection-tool-context.ts +19 -0
  165. package/src/ui/headless/selection-tool-resolver.ts +752 -0
  166. package/src/ui/headless/selection-tool-types.ts +129 -0
  167. package/src/ui/headless/selection-toolbar-model.ts +11 -0
  168. package/src/ui/headless/use-editor-keyboard.ts +103 -0
  169. package/src/ui/review/README.md +3 -0
  170. package/src/ui/runtime-shortcut-dispatch.ts +365 -0
  171. package/src/ui/runtime-snapshot-selectors.ts +197 -0
  172. package/src/ui/shared/revision-filters.ts +31 -0
  173. package/src/ui/status/README.md +3 -0
  174. package/src/ui/theme/README.md +3 -0
  175. package/src/ui/toolbar/README.md +3 -0
  176. package/src/ui/workflow-surface-blocked-rails.ts +94 -0
  177. package/src/ui-tailwind/chrome/chrome-preset-model.ts +107 -0
  178. package/src/ui-tailwind/chrome/chrome-preset-toolbar.tsx +15 -0
  179. package/src/ui-tailwind/chrome/responsive-chrome.ts +46 -0
  180. package/src/ui-tailwind/chrome/review-queue-bar.tsx +97 -0
  181. package/src/ui-tailwind/chrome/tw-alert-banner.tsx +64 -0
  182. package/src/ui-tailwind/chrome/tw-context-analytics-summary.tsx +122 -0
  183. package/src/ui-tailwind/chrome/tw-image-context-toolbar.tsx +121 -0
  184. package/src/ui-tailwind/chrome/tw-layout-panel.tsx +114 -0
  185. package/src/ui-tailwind/chrome/tw-object-context-toolbar.tsx +30 -0
  186. package/src/ui-tailwind/chrome/tw-page-ruler.tsx +365 -0
  187. package/src/ui-tailwind/chrome/tw-selection-tool-blocked.tsx +23 -0
  188. package/src/ui-tailwind/chrome/tw-selection-tool-comment.tsx +35 -0
  189. package/src/ui-tailwind/chrome/tw-selection-tool-formatting.tsx +37 -0
  190. package/src/ui-tailwind/chrome/tw-selection-tool-host.tsx +303 -0
  191. package/src/ui-tailwind/chrome/tw-selection-tool-structure.tsx +116 -0
  192. package/src/ui-tailwind/chrome/tw-selection-tool-suggestion.tsx +29 -0
  193. package/src/ui-tailwind/chrome/tw-selection-tool-workflow.tsx +27 -0
  194. package/src/ui-tailwind/chrome/tw-selection-toolbar.tsx +186 -0
  195. package/src/ui-tailwind/chrome/tw-suggestion-card.tsx +139 -0
  196. package/src/ui-tailwind/chrome/tw-table-context-toolbar.tsx +250 -0
  197. package/src/ui-tailwind/chrome/tw-unsaved-modal.tsx +58 -0
  198. package/src/ui-tailwind/chrome/use-before-unload.ts +20 -0
  199. package/src/ui-tailwind/editor-surface/perf-probe.ts +179 -0
  200. package/src/ui-tailwind/editor-surface/pm-command-bridge.ts +189 -0
  201. package/src/ui-tailwind/editor-surface/pm-contextual-ui.ts +31 -0
  202. package/src/ui-tailwind/editor-surface/pm-decorations.ts +411 -0
  203. package/src/ui-tailwind/editor-surface/pm-position-map.ts +123 -0
  204. package/src/ui-tailwind/editor-surface/pm-schema.ts +927 -0
  205. package/src/ui-tailwind/editor-surface/pm-state-from-snapshot.ts +567 -0
  206. package/src/ui-tailwind/editor-surface/search-plugin.ts +168 -0
  207. package/src/ui-tailwind/editor-surface/surface-build-keys.ts +63 -0
  208. package/src/ui-tailwind/editor-surface/tw-caret.tsx +12 -0
  209. package/src/ui-tailwind/editor-surface/tw-editor-surface.tsx +150 -0
  210. package/src/ui-tailwind/editor-surface/tw-inline-token.tsx +129 -0
  211. package/src/ui-tailwind/editor-surface/tw-opaque-block.tsx +58 -0
  212. package/src/ui-tailwind/editor-surface/tw-paragraph-block.tsx +151 -0
  213. package/src/ui-tailwind/editor-surface/tw-prosemirror-surface.tsx +1047 -0
  214. package/src/ui-tailwind/editor-surface/tw-segment-view.tsx +111 -0
  215. package/src/ui-tailwind/editor-surface/tw-table-node-view.tsx +503 -0
  216. package/src/ui-tailwind/index.ts +62 -0
  217. package/src/ui-tailwind/page-chrome-model.ts +27 -0
  218. package/src/ui-tailwind/review/tw-comment-sidebar.tsx +406 -0
  219. package/src/ui-tailwind/review/tw-health-panel.tsx +149 -0
  220. package/src/ui-tailwind/review/tw-review-rail.tsx +130 -0
  221. package/src/ui-tailwind/review/tw-revision-sidebar.tsx +164 -0
  222. package/src/ui-tailwind/status/tw-status-bar.tsx +65 -0
  223. package/{dist → src}/ui-tailwind/theme/editor-theme.css +58 -40
  224. package/src/ui-tailwind/toolbar/toolbar-layout.ts +47 -0
  225. package/src/ui-tailwind/toolbar/tw-toolbar-icon-button.tsx +52 -0
  226. package/src/ui-tailwind/toolbar/tw-toolbar.tsx +1478 -0
  227. package/src/ui-tailwind/tw-review-workspace.tsx +1587 -0
  228. package/src/validation/README.md +3 -0
  229. package/src/validation/compatibility-engine.ts +878 -0
  230. package/src/validation/compatibility-report.ts +161 -0
  231. package/src/validation/diagnostics.ts +204 -0
  232. package/src/validation/docx-comment-proof.ts +720 -0
  233. package/src/validation/import-diagnostics.ts +128 -0
  234. package/src/validation/low-priority-word-surfaces.ts +373 -0
  235. package/dist/canonical-document-BLEbzL2J.d.cts +0 -844
  236. package/dist/canonical-document-BLEbzL2J.d.ts +0 -844
  237. package/dist/chunk-2FJS5GZM.js +0 -763
  238. package/dist/chunk-2FJS5GZM.js.map +0 -1
  239. package/dist/chunk-2OQBZS3F.js +0 -446
  240. package/dist/chunk-2OQBZS3F.js.map +0 -1
  241. package/dist/chunk-2S7W4KFO.js +0 -127
  242. package/dist/chunk-2S7W4KFO.js.map +0 -1
  243. package/dist/chunk-2TG72QSW.js +0 -3874
  244. package/dist/chunk-2TG72QSW.js.map +0 -1
  245. package/dist/chunk-36QNIZBO.js +0 -532
  246. package/dist/chunk-36QNIZBO.js.map +0 -1
  247. package/dist/chunk-4AQOYAW4.js +0 -3069
  248. package/dist/chunk-4AQOYAW4.js.map +0 -1
  249. package/dist/chunk-4D5EWJ3P.js +0 -77
  250. package/dist/chunk-4D5EWJ3P.js.map +0 -1
  251. package/dist/chunk-5FN54NDH.js +0 -2257
  252. package/dist/chunk-5FN54NDH.js.map +0 -1
  253. package/dist/chunk-BOYGQYRQ.js +0 -7306
  254. package/dist/chunk-BOYGQYRQ.js.map +0 -1
  255. package/dist/chunk-CN3XMECL.js +0 -212
  256. package/dist/chunk-CN3XMECL.js.map +0 -1
  257. package/dist/chunk-EBI3BX6U.js +0 -164
  258. package/dist/chunk-EBI3BX6U.js.map +0 -1
  259. package/dist/chunk-EILUG3VB.js +0 -1275
  260. package/dist/chunk-EILUG3VB.js.map +0 -1
  261. package/dist/chunk-FUDY333O.js +0 -70
  262. package/dist/chunk-FUDY333O.js.map +0 -1
  263. package/dist/chunk-GBVOWFIK.js +0 -1237
  264. package/dist/chunk-GBVOWFIK.js.map +0 -1
  265. package/dist/chunk-H4TQ3H3Y.js +0 -262
  266. package/dist/chunk-H4TQ3H3Y.js.map +0 -1
  267. package/dist/chunk-JGB3IXZO.js +0 -189
  268. package/dist/chunk-JGB3IXZO.js.map +0 -1
  269. package/dist/chunk-KD2QRQPY.js +0 -4342
  270. package/dist/chunk-KD2QRQPY.js.map +0 -1
  271. package/dist/chunk-KLMXQVYK.js +0 -369
  272. package/dist/chunk-KLMXQVYK.js.map +0 -1
  273. package/dist/chunk-KZUG5KFQ.js +0 -214
  274. package/dist/chunk-KZUG5KFQ.js.map +0 -1
  275. package/dist/chunk-QDAQ4CJU.js +0 -345
  276. package/dist/chunk-QDAQ4CJU.js.map +0 -1
  277. package/dist/chunk-RMH72RZI.js.map +0 -1
  278. package/dist/chunk-SWKWQZXM.js +0 -117
  279. package/dist/chunk-SWKWQZXM.js.map +0 -1
  280. package/dist/chunk-TJBP2K4T.js.map +0 -1
  281. package/dist/chunk-TLCEAQDQ.js +0 -542
  282. package/dist/chunk-TLCEAQDQ.js.map +0 -1
  283. package/dist/chunk-UZXBISGO.js.map +0 -1
  284. package/dist/chunk-WGBAKP3Q.js +0 -3220
  285. package/dist/chunk-WGBAKP3Q.js.map +0 -1
  286. package/dist/compare/index.cjs +0 -5475
  287. package/dist/compare/index.cjs.map +0 -1
  288. package/dist/compare/index.d.cts +0 -114
  289. package/dist/compare/index.d.ts +0 -114
  290. package/dist/compare/index.js +0 -731
  291. package/dist/compare/index.js.map +0 -1
  292. package/dist/core/commands/formatting-commands.cjs +0 -828
  293. package/dist/core/commands/formatting-commands.cjs.map +0 -1
  294. package/dist/core/commands/formatting-commands.d.cts +0 -63
  295. package/dist/core/commands/formatting-commands.d.ts +0 -63
  296. package/dist/core/commands/formatting-commands.js +0 -37
  297. package/dist/core/commands/formatting-commands.js.map +0 -1
  298. package/dist/core/commands/image-commands.cjs +0 -2023
  299. package/dist/core/commands/image-commands.cjs.map +0 -1
  300. package/dist/core/commands/image-commands.d.cts +0 -58
  301. package/dist/core/commands/image-commands.d.ts +0 -58
  302. package/dist/core/commands/image-commands.js +0 -18
  303. package/dist/core/commands/image-commands.js.map +0 -1
  304. package/dist/core/commands/section-layout-commands.cjs.map +0 -1
  305. package/dist/core/commands/section-layout-commands.d.cts +0 -62
  306. package/dist/core/commands/section-layout-commands.d.ts +0 -62
  307. package/dist/core/commands/section-layout-commands.js +0 -21
  308. package/dist/core/commands/section-layout-commands.js.map +0 -1
  309. package/dist/core/commands/style-commands.cjs.map +0 -1
  310. package/dist/core/commands/style-commands.d.cts +0 -13
  311. package/dist/core/commands/style-commands.d.ts +0 -13
  312. package/dist/core/commands/style-commands.js +0 -9
  313. package/dist/core/commands/style-commands.js.map +0 -1
  314. package/dist/core/commands/table-structure-commands.cjs +0 -1883
  315. package/dist/core/commands/table-structure-commands.cjs.map +0 -1
  316. package/dist/core/commands/table-structure-commands.d.cts +0 -59
  317. package/dist/core/commands/table-structure-commands.d.ts +0 -59
  318. package/dist/core/commands/table-structure-commands.js +0 -12
  319. package/dist/core/commands/table-structure-commands.js.map +0 -1
  320. package/dist/core/commands/text-commands.cjs +0 -2391
  321. package/dist/core/commands/text-commands.cjs.map +0 -1
  322. package/dist/core/commands/text-commands.d.cts +0 -24
  323. package/dist/core/commands/text-commands.d.ts +0 -24
  324. package/dist/core/commands/text-commands.js +0 -28
  325. package/dist/core/commands/text-commands.js.map +0 -1
  326. package/dist/core/selection/mapping.cjs +0 -200
  327. package/dist/core/selection/mapping.cjs.map +0 -1
  328. package/dist/core/selection/mapping.d.cts +0 -2
  329. package/dist/core/selection/mapping.d.ts +0 -2
  330. package/dist/core/selection/mapping.js +0 -31
  331. package/dist/core/selection/mapping.js.map +0 -1
  332. package/dist/core/state/editor-state.cjs +0 -2278
  333. package/dist/core/state/editor-state.cjs.map +0 -1
  334. package/dist/core/state/editor-state.d.cts +0 -2
  335. package/dist/core/state/editor-state.d.ts +0 -2
  336. package/dist/core/state/editor-state.js +0 -26
  337. package/dist/core/state/editor-state.js.map +0 -1
  338. package/dist/index.cjs +0 -38553
  339. package/dist/index.cjs.map +0 -1
  340. package/dist/index.d.cts +0 -15
  341. package/dist/index.d.ts +0 -15
  342. package/dist/index.js +0 -7856
  343. package/dist/index.js.map +0 -1
  344. package/dist/io/docx-session.cjs +0 -16236
  345. package/dist/io/docx-session.cjs.map +0 -1
  346. package/dist/io/docx-session.d.cts +0 -21
  347. package/dist/io/docx-session.d.ts +0 -21
  348. package/dist/io/docx-session.js +0 -18
  349. package/dist/io/docx-session.js.map +0 -1
  350. package/dist/legal/index.cjs +0 -3900
  351. package/dist/legal/index.cjs.map +0 -1
  352. package/dist/legal/index.d.cts +0 -86
  353. package/dist/legal/index.d.ts +0 -86
  354. package/dist/legal/index.js +0 -616
  355. package/dist/legal/index.js.map +0 -1
  356. package/dist/public-types-7ZL_94cz.d.ts +0 -1573
  357. package/dist/public-types-CeMaDueh.d.cts +0 -1573
  358. package/dist/public-types.cjs +0 -19
  359. package/dist/public-types.cjs.map +0 -1
  360. package/dist/public-types.d.cts +0 -2
  361. package/dist/public-types.d.ts +0 -2
  362. package/dist/public-types.js +0 -1
  363. package/dist/public-types.js.map +0 -1
  364. package/dist/runtime/document-runtime.cjs +0 -11140
  365. package/dist/runtime/document-runtime.cjs.map +0 -1
  366. package/dist/runtime/document-runtime.d.cts +0 -231
  367. package/dist/runtime/document-runtime.d.ts +0 -231
  368. package/dist/runtime/document-runtime.js +0 -21
  369. package/dist/runtime/document-runtime.js.map +0 -1
  370. package/dist/structural-helpers-CilgOVhh.d.cts +0 -10
  371. package/dist/structural-helpers-q0Gd-eBN.d.ts +0 -10
  372. package/dist/ui-tailwind/editor-surface/search-plugin.cjs +0 -313
  373. package/dist/ui-tailwind/editor-surface/search-plugin.cjs.map +0 -1
  374. package/dist/ui-tailwind/editor-surface/search-plugin.d.cts +0 -67
  375. package/dist/ui-tailwind/editor-surface/search-plugin.d.ts +0 -67
  376. package/dist/ui-tailwind/editor-surface/search-plugin.js +0 -23
  377. package/dist/ui-tailwind/editor-surface/search-plugin.js.map +0 -1
  378. package/dist/ui-tailwind/index.cjs +0 -4833
  379. package/dist/ui-tailwind/index.cjs.map +0 -1
  380. package/dist/ui-tailwind/index.d.cts +0 -617
  381. package/dist/ui-tailwind/index.d.ts +0 -617
  382. package/dist/ui-tailwind/index.js +0 -575
  383. package/dist/ui-tailwind/index.js.map +0 -1
@@ -0,0 +1,2722 @@
1
+ import {
2
+ CDS_SCHEMA_VERSION,
3
+ type ISO8601DateTime,
4
+ type ModelValidationIssue,
5
+ type UUID,
6
+ asPlainObject,
7
+ assertValid,
8
+ expectExactString,
9
+ expectIso8601UtcTimestamp,
10
+ expectString,
11
+ expectStringAllowEmpty,
12
+ expectUuid,
13
+ stableStringify,
14
+ } from "./cds-1.0.0.ts";
15
+
16
+ const CANONICAL_DOCUMENT_TOP_LEVEL_KEYS = [
17
+ "schemaVersion",
18
+ "docId",
19
+ "createdAt",
20
+ "updatedAt",
21
+ "metadata",
22
+ "styles",
23
+ "numbering",
24
+ "media",
25
+ "content",
26
+ "review",
27
+ "preservation",
28
+ "diagnostics",
29
+ ] as const;
30
+
31
+ const CANONICAL_DOCUMENT_OPTIONAL_KEYS = ["subParts", "fieldRegistry"] as const;
32
+
33
+ const ID_PATTERNS = {
34
+ styleId: /^[A-Za-z_][A-Za-z0-9._-]{0,127}$/,
35
+ abstractNumberingId: /^abstract-num:[A-Za-z0-9._-]{1,120}$/,
36
+ numberingInstanceId: /^num:[A-Za-z0-9._-]{1,120}$/,
37
+ mediaId: /^media:[A-Za-z0-9._/-]{1,120}$/,
38
+ commentId:
39
+ /^(?:comment:[A-Za-z0-9._-]{1,120}|comment-[A-Za-z0-9._-]{1,120}|[0-9]{1,18})$/,
40
+ revisionId:
41
+ /^(?:revision:[A-Za-z0-9._-]{1,120}|change-[A-Za-z0-9._-]{1,120})$/,
42
+ fragmentId: /^fragment:[A-Za-z0-9._-]{1,120}$/,
43
+ warningId: /^warning:[A-Za-z0-9._:-]{1,120}$/,
44
+ diagnosticId: /^diagnostic:[A-Za-z0-9._-]{1,120}$/,
45
+ packagePartName: /^\/[A-Za-z0-9_.\-\/]+\.[A-Za-z0-9]+$/,
46
+ relationshipId: /^rId[A-Za-z0-9._-]{1,120}$/,
47
+ } as const;
48
+
49
+ type StableIdDomain = keyof typeof ID_PATTERNS;
50
+
51
+ export interface CanonicalDocument {
52
+ schemaVersion: typeof CDS_SCHEMA_VERSION;
53
+ docId: UUID;
54
+ createdAt: ISO8601DateTime;
55
+ updatedAt: ISO8601DateTime;
56
+ metadata: DocumentMetadata;
57
+ styles: StylesCatalog;
58
+ numbering: NumberingCatalog;
59
+ media: MediaCatalog;
60
+ content: DocumentRootNode;
61
+ review: ReviewStore;
62
+ preservation: PreservationStore;
63
+ diagnostics: DiagnosticStore;
64
+ subParts?: SubPartsCatalog;
65
+ /** Package-backed field registry for supported field families. */
66
+ fieldRegistry?: FieldRegistry;
67
+ }
68
+
69
+ export interface DocumentMetadata {
70
+ title?: string;
71
+ subject?: string;
72
+ description?: string;
73
+ language?: string;
74
+ keywords?: string[];
75
+ category?: string;
76
+ importMode?: string;
77
+ customProperties: Record<string, string>;
78
+ }
79
+
80
+ export interface StylesCatalog {
81
+ paragraphs: Record<string, ParagraphStyleDefinition>;
82
+ characters: Record<string, CharacterStyleDefinition>;
83
+ tables: Record<string, TableStyleDefinition>;
84
+ latentStyles?: Record<string, LatentStyleDefinition>;
85
+ fromPackage?: boolean;
86
+ }
87
+
88
+ export interface ParagraphStyleDefinition {
89
+ styleId: string;
90
+ basedOn?: string;
91
+ nextStyle?: string;
92
+ outlineLevel?: number;
93
+ numbering?: ParagraphStyleNumberingReference;
94
+ displayName: string;
95
+ kind: "paragraph";
96
+ isDefault: boolean;
97
+ }
98
+
99
+ export interface ParagraphStyleNumberingReference {
100
+ numberingInstanceId: string;
101
+ level?: number;
102
+ }
103
+
104
+ export interface CharacterStyleDefinition {
105
+ styleId: string;
106
+ basedOn?: string;
107
+ displayName: string;
108
+ kind: "character";
109
+ isDefault: boolean;
110
+ }
111
+
112
+ export interface TableStyleDefinition {
113
+ styleId: string;
114
+ basedOn?: string;
115
+ displayName: string;
116
+ kind: "table";
117
+ isDefault: boolean;
118
+ formatting?: TableStyleFormatting;
119
+ conditionalFormatting?: Partial<Record<TableStyleConditionalRegion, TableStyleFormatting>>;
120
+ }
121
+
122
+ export interface LatentStyleDefinition {
123
+ name: string;
124
+ locked?: boolean;
125
+ semiHidden?: boolean;
126
+ unhideWhenUsed?: boolean;
127
+ qFormat?: boolean;
128
+ uiPriority?: number;
129
+ }
130
+
131
+ export interface NumberingCatalog {
132
+ abstractDefinitions: Record<string, AbstractNumberingDefinition>;
133
+ instances: Record<string, NumberingInstance>;
134
+ }
135
+
136
+ export interface AbstractNumberingDefinition {
137
+ abstractNumberingId: string;
138
+ levels: NumberingLevelDefinition[];
139
+ }
140
+
141
+ export interface NumberingLevelParagraphGeometry {
142
+ justification?: "left" | "center" | "right" | "both" | "distribute";
143
+ spacing?: ParagraphSpacing;
144
+ indentation?: ParagraphIndentation;
145
+ tabStops?: TabStop[];
146
+ }
147
+
148
+ export interface NumberingLevelDefinition {
149
+ level: number;
150
+ format: string;
151
+ text: string;
152
+ startAt?: number;
153
+ paragraphStyleId?: string;
154
+ isLegalNumbering?: boolean;
155
+ suffix?: "tab" | "space" | "nothing";
156
+ paragraphGeometry?: NumberingLevelParagraphGeometry;
157
+ }
158
+
159
+ export interface NumberingLevelOverrideDefinition {
160
+ level: number;
161
+ format?: string;
162
+ text?: string;
163
+ startAt?: number;
164
+ paragraphStyleId?: string;
165
+ isLegalNumbering?: boolean;
166
+ suffix?: "tab" | "space" | "nothing";
167
+ paragraphGeometry?: NumberingLevelParagraphGeometry;
168
+ }
169
+
170
+ export interface NumberingInstance {
171
+ numberingInstanceId: string;
172
+ abstractNumberingId: string;
173
+ overrides: NumberingLevelOverride[];
174
+ }
175
+
176
+ export interface NumberingLevelOverride {
177
+ level: number;
178
+ startAt?: number;
179
+ levelDefinition?: NumberingLevelOverrideDefinition;
180
+ }
181
+
182
+ export interface MediaCatalog {
183
+ items: Record<string, MediaItem>;
184
+ }
185
+
186
+ export interface MediaItem {
187
+ mediaId: string;
188
+ contentType: string;
189
+ filename: string;
190
+ relationshipId?: string;
191
+ packagePartName: string;
192
+ altText?: string;
193
+ display?: "inline" | "floating";
194
+ widthEmu?: number;
195
+ heightEmu?: number;
196
+ }
197
+
198
+ // ---- Sub-part canonical types ----
199
+
200
+ export type HeaderFooterVariant = "default" | "first" | "even";
201
+
202
+ export interface HeaderDocument {
203
+ variant: HeaderFooterVariant;
204
+ partPath: string;
205
+ relationshipId: string;
206
+ blocks: BlockNode[];
207
+ sectionIndex?: number;
208
+ }
209
+
210
+ export interface FooterDocument {
211
+ variant: HeaderFooterVariant;
212
+ partPath: string;
213
+ relationshipId: string;
214
+ blocks: BlockNode[];
215
+ sectionIndex?: number;
216
+ }
217
+
218
+ export interface FootnoteDefinition {
219
+ noteId: string;
220
+ kind: "footnote" | "endnote";
221
+ blocks: BlockNode[];
222
+ }
223
+
224
+ export interface FootnoteCollection {
225
+ footnotes: Record<string, FootnoteDefinition>;
226
+ endnotes: Record<string, FootnoteDefinition>;
227
+ }
228
+
229
+ export interface ThemeColorScheme {
230
+ name: string;
231
+ colors: Record<string, string>;
232
+ }
233
+
234
+ export interface ThemeFontScheme {
235
+ name: string;
236
+ majorFont?: string;
237
+ minorFont?: string;
238
+ }
239
+
240
+ export interface ThemeDefinition {
241
+ name?: string;
242
+ colorScheme?: ThemeColorScheme;
243
+ fontScheme?: ThemeFontScheme;
244
+ }
245
+
246
+ export interface DocumentSettings {
247
+ evenAndOddHeaders?: boolean;
248
+ zoomLevel?: "pageWidth" | "onePage" | number;
249
+ }
250
+
251
+ export interface SubPartsCatalog {
252
+ headers: HeaderDocument[];
253
+ footers: FooterDocument[];
254
+ footnoteCollection?: FootnoteCollection;
255
+ theme?: ThemeDefinition;
256
+ finalSectionProperties?: SectionProperties;
257
+ resolvedTheme?: ResolvedTheme;
258
+ settings?: DocumentSettings;
259
+ }
260
+
261
+ export interface ResolvedTheme {
262
+ colors: Record<string, string>;
263
+ majorFont?: string;
264
+ minorFont?: string;
265
+ }
266
+
267
+ // ---- Inline footnote reference node ----
268
+
269
+ export interface FootnoteRefNode {
270
+ type: "footnote_ref";
271
+ noteId: string;
272
+ noteKind: "footnote" | "endnote";
273
+ }
274
+
275
+ export type DocumentNode =
276
+ | DocumentRootNode
277
+ | ParagraphNode
278
+ | TableNode
279
+ | TableRowNode
280
+ | TableCellNode
281
+ | SdtNode
282
+ | CustomXmlNode
283
+ | AltChunkNode
284
+ | TextNode
285
+ | HardBreakNode
286
+ | TabNode
287
+ | ColumnBreakNode
288
+ | SymbolNode
289
+ | HyperlinkNode
290
+ | ImageNode
291
+ | FieldNode
292
+ | BookmarkStartNode
293
+ | BookmarkEndNode
294
+ | SectionBreakNode
295
+ | OpaqueInlineNode
296
+ | OpaqueBlockNode
297
+ | FootnoteRefNode
298
+ | ChartPreviewNode
299
+ | SmartArtPreviewNode
300
+ | ShapeNode
301
+ | WordArtNode
302
+ | VmlShapeNode;
303
+
304
+ export interface DocumentRootNode {
305
+ type: "doc";
306
+ children: BlockNode[];
307
+ }
308
+
309
+ export type BlockNode =
310
+ | ParagraphNode
311
+ | TableNode
312
+ | SdtNode
313
+ | CustomXmlNode
314
+ | AltChunkNode
315
+ | SectionBreakNode
316
+ | OpaqueBlockNode;
317
+
318
+ export interface ParagraphSpacing {
319
+ before?: number;
320
+ after?: number;
321
+ line?: number;
322
+ lineRule?: "auto" | "exact" | "atLeast";
323
+ }
324
+
325
+ export interface ParagraphIndentation {
326
+ left?: number;
327
+ right?: number;
328
+ firstLine?: number;
329
+ hanging?: number;
330
+ }
331
+
332
+ export interface TabStop {
333
+ position: number;
334
+ align: "left" | "center" | "right" | "decimal" | "num" | "bar" | "clear";
335
+ leader?: "none" | "dot" | "hyphen" | "underscore" | "heavy" | "middleDot";
336
+ }
337
+
338
+ export interface ParagraphBorders {
339
+ top?: BorderSpec;
340
+ left?: BorderSpec;
341
+ bottom?: BorderSpec;
342
+ right?: BorderSpec;
343
+ bar?: BorderSpec;
344
+ between?: BorderSpec;
345
+ }
346
+
347
+ export interface ParagraphShading {
348
+ fill?: string;
349
+ color?: string;
350
+ val?: string;
351
+ }
352
+
353
+ export interface ParagraphNode {
354
+ type: "paragraph";
355
+ styleId?: string;
356
+ numbering?: {
357
+ numberingInstanceId: string;
358
+ level: number;
359
+ };
360
+ alignment?: "left" | "center" | "right" | "both" | "distribute";
361
+ spacing?: ParagraphSpacing;
362
+ contextualSpacing?: boolean;
363
+ indentation?: ParagraphIndentation;
364
+ tabStops?: TabStop[];
365
+ keepNext?: boolean;
366
+ keepLines?: boolean;
367
+ outlineLevel?: number;
368
+ pageBreakBefore?: boolean;
369
+ widowControl?: boolean;
370
+ borders?: ParagraphBorders;
371
+ shading?: ParagraphShading;
372
+ bidi?: boolean;
373
+ suppressLineNumbers?: boolean;
374
+ cnfStyle?: string;
375
+ children: InlineNode[];
376
+ }
377
+
378
+ export interface BorderSpec {
379
+ value?: string;
380
+ size?: number;
381
+ space?: number;
382
+ color?: string;
383
+ }
384
+
385
+ export interface TableBorders {
386
+ top?: BorderSpec;
387
+ left?: BorderSpec;
388
+ bottom?: BorderSpec;
389
+ right?: BorderSpec;
390
+ insideH?: BorderSpec;
391
+ insideV?: BorderSpec;
392
+ }
393
+
394
+ export interface TableCellBorders {
395
+ top?: BorderSpec;
396
+ left?: BorderSpec;
397
+ bottom?: BorderSpec;
398
+ right?: BorderSpec;
399
+ insideH?: BorderSpec;
400
+ insideV?: BorderSpec;
401
+ }
402
+
403
+ export interface TableWidth {
404
+ value: number;
405
+ type: "dxa" | "auto" | "pct" | "nil";
406
+ }
407
+
408
+ export interface CellShading {
409
+ fill?: string;
410
+ color?: string;
411
+ val?: string;
412
+ }
413
+
414
+ export interface TableCellMargins {
415
+ top?: number;
416
+ left?: number;
417
+ bottom?: number;
418
+ right?: number;
419
+ }
420
+
421
+ export interface TableLook {
422
+ val?: string;
423
+ firstRow?: boolean;
424
+ lastRow?: boolean;
425
+ firstColumn?: boolean;
426
+ lastColumn?: boolean;
427
+ noHBand?: boolean;
428
+ noVBand?: boolean;
429
+ }
430
+
431
+ export type TableStyleConditionalRegion =
432
+ | "firstRow"
433
+ | "lastRow"
434
+ | "firstColumn"
435
+ | "lastColumn"
436
+ | "band1Horz"
437
+ | "band2Horz"
438
+ | "band1Vert"
439
+ | "band2Vert";
440
+
441
+ export interface TableStyleFormatting {
442
+ table?: {
443
+ width?: TableWidth;
444
+ alignment?: "left" | "center" | "right";
445
+ borders?: TableBorders;
446
+ cellMargins?: TableCellMargins;
447
+ tblLook?: TableLook;
448
+ };
449
+ row?: {
450
+ height?: number;
451
+ heightRule?: "auto" | "atLeast" | "exact";
452
+ isHeader?: boolean;
453
+ };
454
+ cell?: {
455
+ width?: TableWidth;
456
+ borders?: TableCellBorders;
457
+ shading?: CellShading;
458
+ verticalAlign?: "top" | "center" | "bottom";
459
+ };
460
+ }
461
+
462
+ export interface TableNode {
463
+ type: "table";
464
+ styleId?: string;
465
+ propertiesXml?: string;
466
+ gridColumns: number[];
467
+ rows: TableRowNode[];
468
+ width?: TableWidth;
469
+ alignment?: "left" | "center" | "right";
470
+ borders?: TableBorders;
471
+ cellMargins?: TableCellMargins;
472
+ tblLook?: TableLook;
473
+ }
474
+
475
+ export interface TableRowNode {
476
+ type: "table_row";
477
+ propertiesXml?: string;
478
+ cells: TableCellNode[];
479
+ gridBefore?: number;
480
+ widthBefore?: TableWidth;
481
+ gridAfter?: number;
482
+ widthAfter?: TableWidth;
483
+ height?: number;
484
+ heightRule?: "auto" | "atLeast" | "exact";
485
+ isHeader?: boolean;
486
+ }
487
+
488
+ export interface TableCellNode {
489
+ type: "table_cell";
490
+ propertiesXml?: string;
491
+ gridSpan?: number;
492
+ verticalMerge?: "restart" | "continue";
493
+ children: BlockNode[];
494
+ width?: TableWidth;
495
+ borders?: TableCellBorders;
496
+ shading?: CellShading;
497
+ verticalAlign?: "top" | "center" | "bottom";
498
+ }
499
+
500
+ export interface SdtCheckboxState {
501
+ checked: boolean;
502
+ checkedChar?: string;
503
+ uncheckedChar?: string;
504
+ }
505
+
506
+ export interface SdtDatePickerState {
507
+ fullDate?: string;
508
+ dateFormat?: string;
509
+ lid?: string;
510
+ }
511
+
512
+ export interface SdtDropdownListItem {
513
+ displayText?: string;
514
+ value: string;
515
+ }
516
+
517
+ export interface SdtNode {
518
+ type: "sdt";
519
+ properties: {
520
+ sdtType?: string;
521
+ alias?: string;
522
+ tag?: string;
523
+ lock?: string;
524
+ propertiesXml?: string;
525
+ checkbox?: SdtCheckboxState;
526
+ datePicker?: SdtDatePickerState;
527
+ dropdownList?: SdtDropdownListItem[];
528
+ comboBox?: SdtDropdownListItem[];
529
+ showingPlcHdr?: boolean;
530
+ };
531
+ children: BlockNode[];
532
+ }
533
+
534
+ export interface CustomXmlNode {
535
+ type: "custom_xml";
536
+ uri?: string;
537
+ element?: string;
538
+ rawXml?: string;
539
+ children: BlockNode[];
540
+ }
541
+
542
+ export interface AltChunkNode {
543
+ type: "alt_chunk";
544
+ relationshipId: string;
545
+ }
546
+
547
+ /**
548
+ * Supported field families that receive first-class canonical treatment.
549
+ * These families have stable registry IDs, dependency metadata, and
550
+ * runtime-owned refresh behavior.
551
+ */
552
+ export type SupportedFieldFamily = "REF" | "PAGEREF" | "NOTEREF" | "TOC";
553
+
554
+ /**
555
+ * Unsupported field families that remain preserve-only.
556
+ * They survive round-trip but do not participate in runtime refresh.
557
+ */
558
+ export type PreserveOnlyFieldFamily =
559
+ | "PAGE"
560
+ | "NUMPAGES"
561
+ | "DATE"
562
+ | "TIME"
563
+ | "AUTHOR"
564
+ | "FILENAME"
565
+ | "MERGEFIELD"
566
+ | "IF"
567
+ | "SEQ"
568
+ | "INDEX"
569
+ | "TC"
570
+ | "STYLEREF"
571
+ | "FORMULA"
572
+ | "UNKNOWN";
573
+
574
+ export type FieldFamily = SupportedFieldFamily | PreserveOnlyFieldFamily;
575
+
576
+ /** Runtime refresh status for a field instance. */
577
+ export type FieldRefreshStatus =
578
+ | "current"
579
+ | "stale"
580
+ | "unresolvable"
581
+ | "preserve-only";
582
+
583
+ export interface FieldNode {
584
+ type: "field";
585
+ fieldType: "simple" | "complex";
586
+ instruction: string;
587
+ children: InlineNode[];
588
+ /** Classified field family. Undefined for legacy snapshots. */
589
+ fieldFamily?: FieldFamily;
590
+ /** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
591
+ fieldTarget?: string;
592
+ /** Runtime refresh status. Undefined for legacy or preserve-only fields. */
593
+ refreshStatus?: FieldRefreshStatus;
594
+ }
595
+
596
+ // ─── Field registry ─────────────────────────────────────────────────────────
597
+
598
+ /**
599
+ * Package-backed field registry that catalogs every field instance in the
600
+ * document, grouped by supported vs preserve-only families.
601
+ *
602
+ * Supported field entries carry dependency metadata (bookmark targets) and
603
+ * participate in deterministic refresh. Preserve-only entries survive
604
+ * round-trip but are not refreshable.
605
+ */
606
+ export interface FieldRegistry {
607
+ /** Supported field instances that participate in refresh. */
608
+ supported: FieldRegistryEntry[];
609
+ /** Preserve-only field instances cataloged for round-trip safety. */
610
+ preserveOnly: FieldRegistryEntry[];
611
+ /** Generated TOC structure extracted from heading-driven TOC fields. */
612
+ tocStructure?: TocStructure;
613
+ }
614
+
615
+ export interface FieldRegistryEntry {
616
+ /** Stable document-order index of this field (0-based). */
617
+ fieldIndex: number;
618
+ /** Classified field family. */
619
+ fieldFamily: FieldFamily;
620
+ /** Whether the field is in the supported refresh slice. */
621
+ supported: boolean;
622
+ /** Field instruction text. */
623
+ instruction: string;
624
+ /** Target bookmark name for REF/PAGEREF/NOTEREF fields. */
625
+ fieldTarget?: string;
626
+ /** Current display text extracted from field content. */
627
+ displayText: string;
628
+ /** Paragraph index in document order where this field appears. */
629
+ paragraphIndex: number;
630
+ /** Runtime refresh status. */
631
+ refreshStatus: FieldRefreshStatus;
632
+ }
633
+
634
+ /**
635
+ * Generated table-of-contents structure extracted from TOC fields and
636
+ * heading-styled paragraphs in the document.
637
+ */
638
+ export interface TocStructure {
639
+ /** The raw TOC field instruction (e.g. "TOC \\o \"1-3\" \\h"). */
640
+ instruction: string;
641
+ /** Heading level range the TOC covers. */
642
+ levelRange: { from: number; to: number };
643
+ /** Ordered TOC entries derived from heading paragraphs. */
644
+ entries: TocEntry[];
645
+ /** Whether the TOC content is current with the heading structure. */
646
+ status: "current" | "stale";
647
+ }
648
+
649
+ export interface TocEntry {
650
+ /** Heading text. */
651
+ text: string;
652
+ /** Heading outline level (1-9). */
653
+ level: number;
654
+ /** Paragraph index of the heading in document order. */
655
+ paragraphIndex: number;
656
+ /** Style ID of the heading paragraph, if available. */
657
+ styleId?: string;
658
+ /** Bookmark name anchoring this heading, if present. */
659
+ bookmarkName?: string;
660
+ }
661
+
662
+ export interface BookmarkStartNode {
663
+ type: "bookmark_start";
664
+ bookmarkId: string;
665
+ name: string;
666
+ }
667
+
668
+ export interface BookmarkEndNode {
669
+ type: "bookmark_end";
670
+ bookmarkId: string;
671
+ }
672
+
673
+ export interface SectionBreakNode {
674
+ type: "section_break";
675
+ sectionPropertiesXml?: string;
676
+ /**
677
+ * @deprecated Legacy field from older snapshots. New exports should use
678
+ * sectionPropertiesXml and only contain raw <w:sectPr> content.
679
+ */
680
+ propertiesXml?: string;
681
+ sectionProperties?: SectionProperties;
682
+ }
683
+
684
+ export interface SectionProperties {
685
+ pageSize?: PageSize;
686
+ pageMargins?: PageMargins;
687
+ columns?: ColumnProperties;
688
+ pageNumbering?: PageNumbering;
689
+ lineNumbering?: SectionLineNumbering;
690
+ pageBorders?: SectionPageBorders;
691
+ documentGrid?: SectionDocumentGrid;
692
+ headerReferences?: HeaderFooterReference[];
693
+ footerReferences?: HeaderFooterReference[];
694
+ sectionType?: "continuous" | "nextPage" | "evenPage" | "oddPage" | "nextColumn";
695
+ titlePage?: boolean;
696
+ }
697
+
698
+ export interface PageSize {
699
+ width: number;
700
+ height: number;
701
+ orientation?: "portrait" | "landscape";
702
+ }
703
+
704
+ export interface PageMargins {
705
+ top: number;
706
+ right: number;
707
+ bottom: number;
708
+ left: number;
709
+ header?: number;
710
+ footer?: number;
711
+ gutter?: number;
712
+ }
713
+
714
+ export interface ColumnProperties {
715
+ count?: number;
716
+ space?: number;
717
+ equalWidth?: boolean;
718
+ columns?: Array<{ width: number; space?: number }>;
719
+ separator?: boolean;
720
+ }
721
+
722
+ export interface PageNumbering {
723
+ format?: string;
724
+ start?: number;
725
+ chapStyle?: string;
726
+ chapSep?: string;
727
+ }
728
+
729
+ export interface SectionLineNumbering {
730
+ countBy?: number;
731
+ start?: number;
732
+ distance?: number;
733
+ restart?: "newPage" | "newSection" | "continuous";
734
+ }
735
+
736
+ export interface SectionPageBorders {
737
+ top?: BorderSpec;
738
+ left?: BorderSpec;
739
+ bottom?: BorderSpec;
740
+ right?: BorderSpec;
741
+ offsetFrom?: "page" | "text";
742
+ display?: "allPages" | "firstPage" | "notFirstPage";
743
+ zOrder?: "front" | "back";
744
+ }
745
+
746
+ export interface SectionDocumentGrid {
747
+ type?: "default" | "lines" | "linesAndChars" | "snapToChars";
748
+ linePitch?: number;
749
+ charSpace?: number;
750
+ }
751
+
752
+ export interface HeaderFooterReference {
753
+ variant: HeaderFooterVariant;
754
+ relationshipId: string;
755
+ }
756
+
757
+ export type InlineNode =
758
+ | TextNode
759
+ | HardBreakNode
760
+ | ColumnBreakNode
761
+ | TabNode
762
+ | SymbolNode
763
+ | HyperlinkNode
764
+ | ImageNode
765
+ | FieldNode
766
+ | BookmarkStartNode
767
+ | BookmarkEndNode
768
+ | OpaqueInlineNode
769
+ | FootnoteRefNode
770
+ | ChartPreviewNode
771
+ | SmartArtPreviewNode
772
+ | ShapeNode
773
+ | WordArtNode
774
+ | VmlShapeNode;
775
+
776
+ export interface TextNode {
777
+ type: "text";
778
+ text: string;
779
+ marks?: TextMark[];
780
+ }
781
+
782
+ export type TextMark =
783
+ | { type: "bold" }
784
+ | { type: "italic" }
785
+ | { type: "underline" }
786
+ | { type: "strikethrough" }
787
+ | { type: "doubleStrikethrough" }
788
+ | { type: "vanish" }
789
+ | { type: "lang"; val: string }
790
+ | { type: "highlight"; color: string; val: string }
791
+ | { type: "backgroundColor"; color: string }
792
+ | { type: "charSpacing"; val: number }
793
+ | { type: "kerning"; val: number }
794
+ | { type: "emboss" }
795
+ | { type: "imprint" }
796
+ | { type: "shadow" }
797
+ | { type: "position"; val: number }
798
+ | { type: "textFill"; xml: string }
799
+ | { type: "fontFamily"; val: string }
800
+ | { type: "fontSize"; val: number }
801
+ | { type: "textColor"; color: string }
802
+ | { type: "smallCaps" }
803
+ | { type: "allCaps" };
804
+
805
+ export interface HardBreakNode {
806
+ type: "hard_break";
807
+ }
808
+
809
+ export interface ColumnBreakNode {
810
+ type: "column_break";
811
+ }
812
+
813
+ export interface TabNode {
814
+ type: "tab";
815
+ }
816
+
817
+ export interface SymbolNode {
818
+ type: "symbol";
819
+ char: string;
820
+ font?: string;
821
+ marks?: TextMark[];
822
+ }
823
+
824
+ export interface HyperlinkNode {
825
+ type: "hyperlink";
826
+ href: string;
827
+ children: Array<TextNode | HardBreakNode | ColumnBreakNode | TabNode | SymbolNode>;
828
+ }
829
+
830
+ export interface ImageNode {
831
+ type: "image";
832
+ mediaId: string;
833
+ altText?: string;
834
+ placementXml?: string;
835
+ display?: "inline" | "floating";
836
+ floating?: FloatingImageProperties;
837
+ }
838
+
839
+ export interface FloatingImageProperties {
840
+ horizontalPosition?: FloatingAxisPosition;
841
+ verticalPosition?: FloatingAxisPosition;
842
+ wrap?: "none" | "square" | "tight" | "through" | "topAndBottom";
843
+ behindDoc?: boolean;
844
+ layoutInCell?: boolean;
845
+ allowOverlap?: boolean;
846
+ }
847
+
848
+ export interface FloatingAxisPosition {
849
+ relativeFrom?: string;
850
+ align?: string;
851
+ offset?: number;
852
+ }
853
+
854
+ export interface OpaqueInlineNode {
855
+ type: "opaque_inline";
856
+ fragmentId: string;
857
+ warningId: string;
858
+ }
859
+
860
+ // ---- Complex rendering inline nodes (read-only previews) ----
861
+
862
+ /**
863
+ * Read-only preview of a chart (c:chart). The original drawing XML is stored in
864
+ * rawXml for lossless round-trip export. If a fallback image was present in
865
+ * mc:AlternateContent it is referenced by previewMediaId.
866
+ */
867
+ export interface ChartPreviewNode {
868
+ type: "chart_preview";
869
+ previewMediaId?: string;
870
+ rawXml: string;
871
+ }
872
+
873
+ /**
874
+ * Read-only preview of a SmartArt diagram (dgm:*). The original drawing XML is
875
+ * stored in rawXml for lossless round-trip export.
876
+ */
877
+ export interface SmartArtPreviewNode {
878
+ type: "smartart_preview";
879
+ previewMediaId?: string;
880
+ rawXml: string;
881
+ }
882
+
883
+ /**
884
+ * Read-only rendering of a wps:wsp WordprocessingShape. Text content is
885
+ * extracted for display. The original drawing XML is preserved in rawXml.
886
+ */
887
+ export interface ShapeNode {
888
+ type: "shape";
889
+ text?: string;
890
+ geometry?: string;
891
+ isTextBox?: boolean;
892
+ rawXml: string;
893
+ }
894
+
895
+ /**
896
+ * Read-only rendering of WordArt — a wps:wsp shape with a text-geometry preset.
897
+ * Text is extracted for display. The original drawing XML is preserved in rawXml.
898
+ */
899
+ export interface WordArtNode {
900
+ type: "wordart";
901
+ text: string;
902
+ geometry?: string;
903
+ rawXml: string;
904
+ }
905
+
906
+ /**
907
+ * Read-only rendering of a VML shape (v:shape, v:rect, v:textbox) from a w:pict
908
+ * element. Text is extracted for display. The original w:pict XML is preserved
909
+ * in rawXml for lossless round-trip export.
910
+ */
911
+ export interface VmlShapeNode {
912
+ type: "vml_shape";
913
+ text?: string;
914
+ shapeType?: string;
915
+ rawXml: string;
916
+ }
917
+
918
+ export interface OpaqueBlockNode {
919
+ type: "opaque_block";
920
+ fragmentId: string;
921
+ warningId: string;
922
+ rawXml?: string;
923
+ }
924
+
925
+ export interface DocRange {
926
+ from: number;
927
+ to: number;
928
+ }
929
+
930
+ export interface BoundaryAssoc {
931
+ start: -1 | 1;
932
+ end: -1 | 1;
933
+ }
934
+
935
+ export type CanonicalAnchor =
936
+ | {
937
+ kind: "range";
938
+ range: DocRange;
939
+ assoc: BoundaryAssoc;
940
+ }
941
+ | {
942
+ kind: "node";
943
+ at: number;
944
+ assoc: -1 | 1;
945
+ }
946
+ | {
947
+ kind: "detached";
948
+ lastKnownRange: DocRange;
949
+ reason: "deleted" | "invalidatedByStructureChange" | "importAmbiguity";
950
+ };
951
+
952
+ export interface ReviewStore {
953
+ comments: Record<string, CommentThread>;
954
+ revisions: Record<string, RevisionRecord>;
955
+ }
956
+
957
+ export interface CommentThread {
958
+ commentId: string;
959
+ status: "open" | "resolved" | "detached";
960
+ anchor: CanonicalAnchor;
961
+ createdAt: ISO8601DateTime;
962
+ createdBy?: string;
963
+ authorId?: string;
964
+ body?: string;
965
+ entries?: CommentEntry[];
966
+ resolution?: CommentResolution;
967
+ resolvedAt?: ISO8601DateTime;
968
+ warningIds: string[];
969
+ isResolved?: boolean;
970
+ metadata?: CommentThreadMetadata;
971
+ }
972
+
973
+ export interface CommentEntry {
974
+ entryId: string;
975
+ authorId: string;
976
+ createdAt: ISO8601DateTime;
977
+ body: string;
978
+ metadata?: CommentEntryMetadata;
979
+ }
980
+
981
+ export interface CommentEntryMetadata {
982
+ ooxmlCommentId?: string;
983
+ paraId?: string;
984
+ parentParaId?: string;
985
+ durableId?: string;
986
+ initials?: string;
987
+ }
988
+
989
+ export interface CommentResolution {
990
+ resolvedAt: ISO8601DateTime;
991
+ resolvedBy: string;
992
+ }
993
+
994
+ export interface CommentThreadMetadata {
995
+ source?: "runtime" | "import";
996
+ rootOoxmlCommentId?: string;
997
+ rootParaId?: string;
998
+ detachedReason?: "incomplete-markers" | "multi-paragraph" | "opaque-region" | "revision-overlap";
999
+ actionabilityNote?: string;
1000
+ }
1001
+
1002
+ export interface RevisionPropertyChangeData {
1003
+ xmlTag: "pPrChange" | "sectPrChange" | "tblPrChange" | "rPrChange";
1004
+ beforeXml: string;
1005
+ }
1006
+
1007
+ export interface RevisionMoveData {
1008
+ moveId: string;
1009
+ direction: "from" | "to";
1010
+ linkedRevisionId?: string;
1011
+ }
1012
+
1013
+ export type RevisionStoryTargetRecord =
1014
+ | { kind: "main" }
1015
+ | {
1016
+ kind: "header";
1017
+ relationshipId: string;
1018
+ variant: "default" | "first" | "even";
1019
+ sectionIndex?: number;
1020
+ }
1021
+ | {
1022
+ kind: "footer";
1023
+ relationshipId: string;
1024
+ variant: "default" | "first" | "even";
1025
+ sectionIndex?: number;
1026
+ }
1027
+ | { kind: "footnote"; noteId: string }
1028
+ | { kind: "endnote"; noteId: string };
1029
+
1030
+ export interface RevisionRecord {
1031
+ changeId: string;
1032
+ kind: "insertion" | "deletion" | "formatting" | "move" | "property-change";
1033
+ anchor: CanonicalAnchor;
1034
+ authorId?: string;
1035
+ createdAt: ISO8601DateTime;
1036
+ warningIds?: string[];
1037
+ metadata?: RevisionMetadataRecord;
1038
+ status: "open" | "accepted" | "rejected" | "detached";
1039
+ }
1040
+
1041
+ export interface RevisionMetadataRecord {
1042
+ source?: "runtime" | "import";
1043
+ storyTarget?: RevisionStoryTargetRecord;
1044
+ preserveOnlyReason?: string;
1045
+ suggestionId?: string;
1046
+ semanticKind?:
1047
+ | "insertion"
1048
+ | "deletion"
1049
+ | "replacement"
1050
+ | "formatting-change"
1051
+ | "paragraph-property-change"
1052
+ | "structural-change"
1053
+ | "object-change";
1054
+ linkedRevisionIds?: string[];
1055
+ predecessorSuggestionId?: string;
1056
+ importedRevisionForm?:
1057
+ | "run-insertion"
1058
+ | "run-deletion"
1059
+ | "paragraph-insertion"
1060
+ | "paragraph-deletion";
1061
+ originalRevisionType?: string;
1062
+ ooxmlRevisionId?: string;
1063
+ propertyChangeData?: RevisionPropertyChangeData;
1064
+ moveData?: RevisionMoveData;
1065
+ }
1066
+
1067
+ export interface PreservationStore {
1068
+ opaqueFragments: Record<string, OpaqueFragmentRecord>;
1069
+ packageParts: Record<string, PreservedPackagePart>;
1070
+ }
1071
+
1072
+ export interface OpaqueFragmentRecord {
1073
+ fragmentId: string;
1074
+ payloadKind: "xml-subtree" | "package-part";
1075
+ payloadReference: string;
1076
+ featureClass: "preserve-only";
1077
+ lastKnownRange: DocRange;
1078
+ warningId: string;
1079
+ packagePartName?: string;
1080
+ relationshipId?: string;
1081
+ }
1082
+
1083
+ export interface PreservedPackagePart {
1084
+ packagePartName: string;
1085
+ contentType: string;
1086
+ relationshipIds: string[];
1087
+ }
1088
+
1089
+ export interface DiagnosticStore {
1090
+ warnings: DiagnosticWarningEntry[];
1091
+ errors: DiagnosticErrorEntry[];
1092
+ }
1093
+
1094
+ export interface DiagnosticWarningEntry {
1095
+ diagnosticId: string;
1096
+ warningId: string;
1097
+ source:
1098
+ | "import"
1099
+ | "runtime"
1100
+ | "review"
1101
+ | "preservation"
1102
+ | "validation"
1103
+ | "export";
1104
+ message: string;
1105
+ }
1106
+
1107
+ export interface DiagnosticErrorEntry {
1108
+ diagnosticId: string;
1109
+ code:
1110
+ | "load_failed"
1111
+ | "import_failed"
1112
+ | "export_failed"
1113
+ | "package_corrupt"
1114
+ | "validation_failed"
1115
+ | "datastore_failed"
1116
+ | "internal_invariant";
1117
+ message: string;
1118
+ isFatal: boolean;
1119
+ source: "import" | "runtime" | "validation" | "datastore" | "host" | "export";
1120
+ details?: unknown;
1121
+ }
1122
+
1123
+ export function createCanonicalDocument(
1124
+ input: Omit<CanonicalDocument, "schemaVersion">,
1125
+ ): CanonicalDocument {
1126
+ const document: CanonicalDocument = {
1127
+ schemaVersion: CDS_SCHEMA_VERSION,
1128
+ ...input,
1129
+ };
1130
+
1131
+ assertCanonicalDocument(document);
1132
+ return document;
1133
+ }
1134
+
1135
+ export function serializeCanonicalDocument(document: CanonicalDocument): string {
1136
+ assertCanonicalDocument(document);
1137
+ return stableStringify(document);
1138
+ }
1139
+
1140
+ export function createCanonicalDocumentSignature(document: unknown): string {
1141
+ return stableStringify(document);
1142
+ }
1143
+
1144
+ export function parseCanonicalDocument(json: string): CanonicalDocument {
1145
+ const parsed = JSON.parse(json) as unknown;
1146
+ assertCanonicalDocument(parsed);
1147
+ return parsed;
1148
+ }
1149
+
1150
+ export function projectCanonicalDocument(
1151
+ document: CanonicalDocument,
1152
+ ): CanonicalDocument {
1153
+ assertCanonicalDocument(document);
1154
+ return JSON.parse(stableStringify(document)) as CanonicalDocument;
1155
+ }
1156
+
1157
+ export function assertCanonicalDocument(
1158
+ value: unknown,
1159
+ ): asserts value is CanonicalDocument {
1160
+ const issues = validateCanonicalDocument(value);
1161
+ assertValid(issues, "Invalid canonical document.");
1162
+ }
1163
+
1164
+ export function validateCanonicalDocument(
1165
+ value: unknown,
1166
+ ): ModelValidationIssue[] {
1167
+ const issues: ModelValidationIssue[] = [];
1168
+ const record = asPlainObject(value, "$", issues);
1169
+ if (!record) {
1170
+ return issues;
1171
+ }
1172
+
1173
+ validateExactObjectKeys(record, CANONICAL_DOCUMENT_TOP_LEVEL_KEYS, "$", issues, CANONICAL_DOCUMENT_OPTIONAL_KEYS);
1174
+ expectExactString(record.schemaVersion, CDS_SCHEMA_VERSION, "$.schemaVersion", issues);
1175
+ expectUuid(record.docId, "$.docId", issues);
1176
+ expectIso8601UtcTimestamp(record.createdAt, "$.createdAt", issues);
1177
+ expectIso8601UtcTimestamp(record.updatedAt, "$.updatedAt", issues);
1178
+
1179
+ validateMetadata(record.metadata, "$.metadata", issues);
1180
+ validateStylesCatalog(record.styles, "$.styles", issues);
1181
+ validateNumberingCatalog(record.numbering, "$.numbering", issues);
1182
+ validateMediaCatalog(record.media, "$.media", issues);
1183
+ validateDocumentNode(record.content, "$.content", issues);
1184
+ validateReviewStore(record.review, "$.review", issues);
1185
+ validatePreservationStore(record.preservation, "$.preservation", issues);
1186
+ validateDiagnosticStore(record.diagnostics, "$.diagnostics", issues);
1187
+ if (record.subParts !== undefined) {
1188
+ validateSubPartsCatalog(record.subParts, "$.subParts", issues);
1189
+ }
1190
+ validateDocumentReferences(record, issues);
1191
+
1192
+ return issues;
1193
+ }
1194
+
1195
+ function validateMetadata(
1196
+ value: unknown,
1197
+ path: string,
1198
+ issues: ModelValidationIssue[],
1199
+ ): void {
1200
+ const record = asPlainObject(value, path, issues);
1201
+ if (!record) {
1202
+ return;
1203
+ }
1204
+
1205
+ const customProperties = asPlainObject(record.customProperties, `${path}.customProperties`, issues);
1206
+ if (!customProperties) {
1207
+ return;
1208
+ }
1209
+
1210
+ for (const [propertyKey, propertyValue] of Object.entries(customProperties)) {
1211
+ if (typeof propertyValue !== "string") {
1212
+ issues.push({
1213
+ path: `${path}.customProperties.${propertyKey}`,
1214
+ message: "customProperties values must be strings.",
1215
+ });
1216
+ }
1217
+ }
1218
+ }
1219
+
1220
+ function validateStylesCatalog(
1221
+ value: unknown,
1222
+ path: string,
1223
+ issues: ModelValidationIssue[],
1224
+ ): void {
1225
+ const record = asPlainObject(value, path, issues);
1226
+ if (!record) {
1227
+ return;
1228
+ }
1229
+
1230
+ validateStyleMap(record.paragraphs, `${path}.paragraphs`, issues);
1231
+ validateStyleMap(record.characters, `${path}.characters`, issues);
1232
+ validateStyleMap(record.tables, `${path}.tables`, issues);
1233
+ if (record.latentStyles !== undefined) {
1234
+ validateLatentStyleMap(record.latentStyles, `${path}.latentStyles`, issues);
1235
+ }
1236
+ }
1237
+
1238
+ function validateStyleMap(
1239
+ value: unknown,
1240
+ path: string,
1241
+ issues: ModelValidationIssue[],
1242
+ ): void {
1243
+ const record = asPlainObject(value, path, issues);
1244
+ if (!record) {
1245
+ return;
1246
+ }
1247
+
1248
+ for (const [styleId, definition] of Object.entries(record)) {
1249
+ expectDomainString(styleId, "styleId", `${path}.${styleId}`, issues);
1250
+ const definitionRecord = asPlainObject(definition, `${path}.${styleId}`, issues);
1251
+ if (!definitionRecord) {
1252
+ continue;
1253
+ }
1254
+ if (definitionRecord.styleId !== styleId) {
1255
+ issues.push({
1256
+ path: `${path}.${styleId}.styleId`,
1257
+ message: "styleId must match the map key.",
1258
+ });
1259
+ }
1260
+ }
1261
+ }
1262
+
1263
+ function validateLatentStyleMap(
1264
+ value: unknown,
1265
+ path: string,
1266
+ issues: ModelValidationIssue[],
1267
+ ): void {
1268
+ const record = asPlainObject(value, path, issues);
1269
+ if (!record) {
1270
+ return;
1271
+ }
1272
+
1273
+ for (const [styleName, definition] of Object.entries(record)) {
1274
+ const definitionRecord = asPlainObject(definition, `${path}.${styleName}`, issues);
1275
+ if (!definitionRecord) {
1276
+ continue;
1277
+ }
1278
+ if (definitionRecord.name !== styleName) {
1279
+ issues.push({
1280
+ path: `${path}.${styleName}.name`,
1281
+ message: "name must match the map key.",
1282
+ });
1283
+ }
1284
+ if (definitionRecord.uiPriority !== undefined && typeof definitionRecord.uiPriority !== "number") {
1285
+ issues.push({
1286
+ path: `${path}.${styleName}.uiPriority`,
1287
+ message: "uiPriority must be a number.",
1288
+ });
1289
+ }
1290
+ }
1291
+ }
1292
+
1293
+ function validateNumberingCatalog(
1294
+ value: unknown,
1295
+ path: string,
1296
+ issues: ModelValidationIssue[],
1297
+ ): void {
1298
+ const record = asPlainObject(value, path, issues);
1299
+ if (!record) {
1300
+ return;
1301
+ }
1302
+
1303
+ const abstractDefinitions = asPlainObject(
1304
+ record.abstractDefinitions,
1305
+ `${path}.abstractDefinitions`,
1306
+ issues,
1307
+ );
1308
+ if (abstractDefinitions) {
1309
+ for (const [abstractId, definition] of Object.entries(abstractDefinitions)) {
1310
+ expectDomainString(
1311
+ abstractId,
1312
+ "abstractNumberingId",
1313
+ `${path}.abstractDefinitions.${abstractId}`,
1314
+ issues,
1315
+ );
1316
+ const definitionRecord = asPlainObject(
1317
+ definition,
1318
+ `${path}.abstractDefinitions.${abstractId}`,
1319
+ issues,
1320
+ );
1321
+ if (definitionRecord && definitionRecord.abstractNumberingId !== abstractId) {
1322
+ issues.push({
1323
+ path: `${path}.abstractDefinitions.${abstractId}.abstractNumberingId`,
1324
+ message: "abstractNumberingId must match the map key.",
1325
+ });
1326
+ }
1327
+ }
1328
+ }
1329
+
1330
+ const instances = asPlainObject(record.instances, `${path}.instances`, issues);
1331
+ if (instances) {
1332
+ for (const [instanceId, instance] of Object.entries(instances)) {
1333
+ expectDomainString(
1334
+ instanceId,
1335
+ "numberingInstanceId",
1336
+ `${path}.instances.${instanceId}`,
1337
+ issues,
1338
+ );
1339
+ const instanceRecord = asPlainObject(
1340
+ instance,
1341
+ `${path}.instances.${instanceId}`,
1342
+ issues,
1343
+ );
1344
+ if (!instanceRecord) {
1345
+ continue;
1346
+ }
1347
+ if (instanceRecord.numberingInstanceId !== instanceId) {
1348
+ issues.push({
1349
+ path: `${path}.instances.${instanceId}.numberingInstanceId`,
1350
+ message: "numberingInstanceId must match the map key.",
1351
+ });
1352
+ }
1353
+ expectDomainString(
1354
+ instanceRecord.abstractNumberingId,
1355
+ "abstractNumberingId",
1356
+ `${path}.instances.${instanceId}.abstractNumberingId`,
1357
+ issues,
1358
+ );
1359
+ }
1360
+ }
1361
+ }
1362
+
1363
+ function validateMediaCatalog(
1364
+ value: unknown,
1365
+ path: string,
1366
+ issues: ModelValidationIssue[],
1367
+ ): void {
1368
+ const record = asPlainObject(value, path, issues);
1369
+ if (!record) {
1370
+ return;
1371
+ }
1372
+
1373
+ const items = asPlainObject(record.items, `${path}.items`, issues);
1374
+ if (!items) {
1375
+ return;
1376
+ }
1377
+
1378
+ for (const [mediaId, item] of Object.entries(items)) {
1379
+ expectDomainString(mediaId, "mediaId", `${path}.items.${mediaId}`, issues);
1380
+ const itemRecord = asPlainObject(item, `${path}.items.${mediaId}`, issues);
1381
+ if (!itemRecord) {
1382
+ continue;
1383
+ }
1384
+ if (itemRecord.mediaId !== mediaId) {
1385
+ issues.push({
1386
+ path: `${path}.items.${mediaId}.mediaId`,
1387
+ message: "mediaId must match the map key.",
1388
+ });
1389
+ }
1390
+ expectDomainString(
1391
+ itemRecord.packagePartName,
1392
+ "packagePartName",
1393
+ `${path}.items.${mediaId}.packagePartName`,
1394
+ issues,
1395
+ );
1396
+ if (itemRecord.relationshipId !== undefined) {
1397
+ expectDomainString(
1398
+ itemRecord.relationshipId,
1399
+ "relationshipId",
1400
+ `${path}.items.${mediaId}.relationshipId`,
1401
+ issues,
1402
+ );
1403
+ }
1404
+ }
1405
+ }
1406
+
1407
+ function validateDocumentNode(
1408
+ value: unknown,
1409
+ path: string,
1410
+ issues: ModelValidationIssue[],
1411
+ ): void {
1412
+ const record = asPlainObject(value, path, issues);
1413
+ if (!record) {
1414
+ return;
1415
+ }
1416
+
1417
+ const type = expectString(record.type, `${path}.type`, issues);
1418
+ if (!type) {
1419
+ return;
1420
+ }
1421
+
1422
+ switch (type) {
1423
+ case "doc":
1424
+ case "paragraph":
1425
+ case "sdt":
1426
+ case "custom_xml":
1427
+ case "hyperlink":
1428
+ if (!Array.isArray(record.children)) {
1429
+ issues.push({ path: `${path}.children`, message: "children must be an array." });
1430
+ } else {
1431
+ record.children.forEach((child, index) =>
1432
+ validateDocumentNode(child, `${path}.children[${index}]`, issues),
1433
+ );
1434
+ }
1435
+ if (type === "paragraph" && record.styleId !== undefined) {
1436
+ expectDomainString(record.styleId, "styleId", `${path}.styleId`, issues);
1437
+ }
1438
+ if (type === "paragraph" && record.numbering !== undefined) {
1439
+ const numbering = asPlainObject(record.numbering, `${path}.numbering`, issues);
1440
+ if (numbering) {
1441
+ expectDomainString(
1442
+ numbering.numberingInstanceId,
1443
+ "numberingInstanceId",
1444
+ `${path}.numbering.numberingInstanceId`,
1445
+ issues,
1446
+ );
1447
+ if (typeof numbering.level !== "number") {
1448
+ issues.push({
1449
+ path: `${path}.numbering.level`,
1450
+ message: "level must be a number.",
1451
+ });
1452
+ }
1453
+ }
1454
+ }
1455
+ if (type === "hyperlink") {
1456
+ expectString(record.href, `${path}.href`, issues);
1457
+ }
1458
+ return;
1459
+ case "alt_chunk":
1460
+ expectDomainString(record.relationshipId, "relationshipId", `${path}.relationshipId`, issues);
1461
+ return;
1462
+ case "image":
1463
+ expectDomainString(record.mediaId, "mediaId", `${path}.mediaId`, issues);
1464
+ if (record.placementXml !== undefined) {
1465
+ expectString(record.placementXml, `${path}.placementXml`, issues);
1466
+ }
1467
+ if (record.display !== undefined) {
1468
+ const display = expectString(record.display, `${path}.display`, issues);
1469
+ if (display && display !== "inline" && display !== "floating") {
1470
+ issues.push({
1471
+ path: `${path}.display`,
1472
+ message: "display must be 'inline' or 'floating'.",
1473
+ });
1474
+ }
1475
+ }
1476
+ if (record.floating !== undefined) {
1477
+ validateFloatingImageProperties(record.floating, `${path}.floating`, issues);
1478
+ }
1479
+ return;
1480
+ case "opaque_inline":
1481
+ case "opaque_block":
1482
+ expectDomainString(record.fragmentId, "fragmentId", `${path}.fragmentId`, issues);
1483
+ expectDomainString(record.warningId, "warningId", `${path}.warningId`, issues);
1484
+ if (record.rawXml !== undefined) {
1485
+ expectString(record.rawXml, `${path}.rawXml`, issues);
1486
+ }
1487
+ return;
1488
+ case "table":
1489
+ if (!Array.isArray(record.gridColumns)) {
1490
+ issues.push({ path: `${path}.gridColumns`, message: "gridColumns must be an array." });
1491
+ }
1492
+ if (!Array.isArray(record.rows)) {
1493
+ issues.push({ path: `${path}.rows`, message: "rows must be an array." });
1494
+ } else {
1495
+ record.rows.forEach((row, rowIndex) =>
1496
+ validateDocumentNode(row, `${path}.rows[${rowIndex}]`, issues),
1497
+ );
1498
+ }
1499
+ return;
1500
+ case "table_row":
1501
+ if (!Array.isArray(record.cells)) {
1502
+ issues.push({ path: `${path}.cells`, message: "cells must be an array." });
1503
+ } else {
1504
+ record.cells.forEach((cell, cellIndex) =>
1505
+ validateDocumentNode(cell, `${path}.cells[${cellIndex}]`, issues),
1506
+ );
1507
+ }
1508
+ return;
1509
+ case "table_cell":
1510
+ if (!Array.isArray(record.children)) {
1511
+ issues.push({ path: `${path}.children`, message: "children must be an array." });
1512
+ } else {
1513
+ record.children.forEach((child, childIndex) =>
1514
+ validateDocumentNode(child, `${path}.children[${childIndex}]`, issues),
1515
+ );
1516
+ }
1517
+ return;
1518
+ case "field":
1519
+ expectString(record.fieldType, `${path}.fieldType`, issues);
1520
+ expectString(record.instruction, `${path}.instruction`, issues);
1521
+ if (!Array.isArray(record.children)) {
1522
+ issues.push({ path: `${path}.children`, message: "children must be an array." });
1523
+ } else {
1524
+ record.children.forEach((child, index) =>
1525
+ validateDocumentNode(child, `${path}.children[${index}]`, issues),
1526
+ );
1527
+ }
1528
+ return;
1529
+ case "bookmark_start":
1530
+ expectString(record.bookmarkId, `${path}.bookmarkId`, issues);
1531
+ expectString(record.name, `${path}.name`, issues);
1532
+ return;
1533
+ case "bookmark_end":
1534
+ expectString(record.bookmarkId, `${path}.bookmarkId`, issues);
1535
+ return;
1536
+ case "section_break":
1537
+ return;
1538
+ case "text":
1539
+ if (typeof record.text !== "string") {
1540
+ issues.push({
1541
+ path: `${path}.text`,
1542
+ message: "text must be a string.",
1543
+ });
1544
+ }
1545
+ return;
1546
+ case "hard_break":
1547
+ case "column_break":
1548
+ case "tab":
1549
+ return;
1550
+ case "symbol":
1551
+ expectString(record.char, `${path}.char`, issues);
1552
+ if (record.font !== undefined) {
1553
+ expectString(record.font, `${path}.font`, issues);
1554
+ }
1555
+ return;
1556
+ case "footnote_ref":
1557
+ expectString(record.noteId, `${path}.noteId`, issues);
1558
+ if (record.noteKind !== "footnote" && record.noteKind !== "endnote") {
1559
+ issues.push({
1560
+ path: `${path}.noteKind`,
1561
+ message: "noteKind must be 'footnote' or 'endnote'.",
1562
+ });
1563
+ }
1564
+ return;
1565
+ case "chart_preview":
1566
+ case "smartart_preview":
1567
+ case "shape":
1568
+ case "wordart":
1569
+ case "vml_shape":
1570
+ expectString(record.rawXml, `${path}.rawXml`, issues);
1571
+ return;
1572
+ default:
1573
+ issues.push({
1574
+ path: `${path}.type`,
1575
+ message: `Unsupported node type ${JSON.stringify(type)}.`,
1576
+ });
1577
+ }
1578
+ }
1579
+
1580
+ function validateFloatingImageProperties(
1581
+ value: unknown,
1582
+ path: string,
1583
+ issues: ModelValidationIssue[],
1584
+ ): void {
1585
+ const record = asPlainObject(value, path, issues);
1586
+ if (!record) {
1587
+ return;
1588
+ }
1589
+
1590
+ if (record.horizontalPosition !== undefined) {
1591
+ validateFloatingAxisPosition(record.horizontalPosition, `${path}.horizontalPosition`, issues);
1592
+ }
1593
+ if (record.verticalPosition !== undefined) {
1594
+ validateFloatingAxisPosition(record.verticalPosition, `${path}.verticalPosition`, issues);
1595
+ }
1596
+ if (record.wrap !== undefined) {
1597
+ const wrap = expectString(record.wrap, `${path}.wrap`, issues);
1598
+ if (
1599
+ wrap &&
1600
+ wrap !== "none" &&
1601
+ wrap !== "square" &&
1602
+ wrap !== "tight" &&
1603
+ wrap !== "through" &&
1604
+ wrap !== "topAndBottom"
1605
+ ) {
1606
+ issues.push({
1607
+ path: `${path}.wrap`,
1608
+ message: "wrap must be one of none, square, tight, through, or topAndBottom.",
1609
+ });
1610
+ }
1611
+ }
1612
+ }
1613
+
1614
+ function validateFloatingAxisPosition(
1615
+ value: unknown,
1616
+ path: string,
1617
+ issues: ModelValidationIssue[],
1618
+ ): void {
1619
+ const record = asPlainObject(value, path, issues);
1620
+ if (!record) {
1621
+ return;
1622
+ }
1623
+
1624
+ if (record.relativeFrom !== undefined) {
1625
+ expectString(record.relativeFrom, `${path}.relativeFrom`, issues);
1626
+ }
1627
+ if (record.align !== undefined) {
1628
+ expectString(record.align, `${path}.align`, issues);
1629
+ }
1630
+ if (record.offset !== undefined && typeof record.offset !== "number") {
1631
+ issues.push({
1632
+ path: `${path}.offset`,
1633
+ message: "offset must be a number.",
1634
+ });
1635
+ }
1636
+ }
1637
+
1638
+ function validateReviewStore(
1639
+ value: unknown,
1640
+ path: string,
1641
+ issues: ModelValidationIssue[],
1642
+ ): void {
1643
+ const record = asPlainObject(value, path, issues);
1644
+ if (!record) {
1645
+ return;
1646
+ }
1647
+
1648
+ const comments = asPlainObject(record.comments, `${path}.comments`, issues);
1649
+ if (comments) {
1650
+ for (const [commentId, thread] of Object.entries(comments)) {
1651
+ expectDomainString(commentId, "commentId", `${path}.comments.${commentId}`, issues);
1652
+ const threadRecord = asPlainObject(thread, `${path}.comments.${commentId}`, issues);
1653
+ if (!threadRecord) {
1654
+ continue;
1655
+ }
1656
+ if (threadRecord.commentId !== commentId) {
1657
+ issues.push({
1658
+ path: `${path}.comments.${commentId}.commentId`,
1659
+ message: "commentId must match the map key.",
1660
+ });
1661
+ }
1662
+ validateAnchor(threadRecord.anchor, `${path}.comments.${commentId}.anchor`, issues);
1663
+ expectIso8601UtcTimestamp(
1664
+ threadRecord.createdAt,
1665
+ `${path}.comments.${commentId}.createdAt`,
1666
+ issues,
1667
+ );
1668
+ validateCommentStatus(threadRecord.status, `${path}.comments.${commentId}.status`, issues);
1669
+ if (threadRecord.createdBy !== undefined) {
1670
+ expectString(
1671
+ threadRecord.createdBy,
1672
+ `${path}.comments.${commentId}.createdBy`,
1673
+ issues,
1674
+ );
1675
+ }
1676
+ if (threadRecord.authorId !== undefined) {
1677
+ expectString(
1678
+ threadRecord.authorId,
1679
+ `${path}.comments.${commentId}.authorId`,
1680
+ issues,
1681
+ );
1682
+ }
1683
+ if (threadRecord.body !== undefined) {
1684
+ expectStringAllowEmpty(
1685
+ threadRecord.body,
1686
+ `${path}.comments.${commentId}.body`,
1687
+ issues,
1688
+ );
1689
+ }
1690
+ if (!Array.isArray(threadRecord.warningIds)) {
1691
+ issues.push({
1692
+ path: `${path}.comments.${commentId}.warningIds`,
1693
+ message: "warningIds must be an array.",
1694
+ });
1695
+ } else {
1696
+ threadRecord.warningIds.forEach((warningId, index) =>
1697
+ expectDomainString(
1698
+ warningId,
1699
+ "warningId",
1700
+ `${path}.comments.${commentId}.warningIds[${index}]`,
1701
+ issues,
1702
+ ),
1703
+ );
1704
+ }
1705
+ if (threadRecord.entries !== undefined) {
1706
+ validateCommentEntries(
1707
+ threadRecord.entries,
1708
+ `${path}.comments.${commentId}.entries`,
1709
+ issues,
1710
+ );
1711
+ }
1712
+ if (threadRecord.resolution !== undefined) {
1713
+ validateCommentResolution(
1714
+ threadRecord.resolution,
1715
+ `${path}.comments.${commentId}.resolution`,
1716
+ issues,
1717
+ );
1718
+ }
1719
+ if (threadRecord.resolvedAt !== undefined) {
1720
+ expectIso8601UtcTimestamp(
1721
+ threadRecord.resolvedAt,
1722
+ `${path}.comments.${commentId}.resolvedAt`,
1723
+ issues,
1724
+ );
1725
+ }
1726
+ if (
1727
+ threadRecord.isResolved !== undefined &&
1728
+ typeof threadRecord.isResolved !== "boolean"
1729
+ ) {
1730
+ issues.push({
1731
+ path: `${path}.comments.${commentId}.isResolved`,
1732
+ message: "isResolved must be a boolean.",
1733
+ });
1734
+ }
1735
+ if (threadRecord.metadata !== undefined) {
1736
+ validateCommentThreadMetadata(
1737
+ threadRecord.metadata,
1738
+ `${path}.comments.${commentId}.metadata`,
1739
+ issues,
1740
+ );
1741
+ }
1742
+ }
1743
+ }
1744
+
1745
+ const revisions = asPlainObject(record.revisions, `${path}.revisions`, issues);
1746
+ if (revisions) {
1747
+ for (const [revisionId, revision] of Object.entries(revisions)) {
1748
+ expectDomainString(revisionId, "revisionId", `${path}.revisions.${revisionId}`, issues);
1749
+ const revisionRecord = asPlainObject(
1750
+ revision,
1751
+ `${path}.revisions.${revisionId}`,
1752
+ issues,
1753
+ );
1754
+ if (!revisionRecord) {
1755
+ continue;
1756
+ }
1757
+ if (revisionRecord.changeId !== revisionId) {
1758
+ issues.push({
1759
+ path: `${path}.revisions.${revisionId}.changeId`,
1760
+ message: "changeId must match the map key.",
1761
+ });
1762
+ }
1763
+ validateAnchor(revisionRecord.anchor, `${path}.revisions.${revisionId}.anchor`, issues);
1764
+ validateRevisionKind(revisionRecord.kind, `${path}.revisions.${revisionId}.kind`, issues);
1765
+ validateRevisionStatus(
1766
+ revisionRecord.status,
1767
+ `${path}.revisions.${revisionId}.status`,
1768
+ issues,
1769
+ );
1770
+ expectIso8601UtcTimestamp(
1771
+ revisionRecord.createdAt,
1772
+ `${path}.revisions.${revisionId}.createdAt`,
1773
+ issues,
1774
+ );
1775
+ if (revisionRecord.authorId !== undefined) {
1776
+ expectString(
1777
+ revisionRecord.authorId,
1778
+ `${path}.revisions.${revisionId}.authorId`,
1779
+ issues,
1780
+ );
1781
+ }
1782
+ if (revisionRecord.warningIds !== undefined) {
1783
+ validateWarningIds(
1784
+ revisionRecord.warningIds,
1785
+ `${path}.revisions.${revisionId}.warningIds`,
1786
+ issues,
1787
+ );
1788
+ }
1789
+ if (revisionRecord.metadata !== undefined) {
1790
+ validateRevisionMetadata(
1791
+ revisionRecord.metadata,
1792
+ `${path}.revisions.${revisionId}.metadata`,
1793
+ issues,
1794
+ );
1795
+ }
1796
+ }
1797
+ }
1798
+ }
1799
+
1800
+ function validateCommentStatus(
1801
+ value: unknown,
1802
+ path: string,
1803
+ issues: ModelValidationIssue[],
1804
+ ): void {
1805
+ if (value === "open" || value === "resolved" || value === "detached") {
1806
+ return;
1807
+ }
1808
+ issues.push({
1809
+ path,
1810
+ message: "status must be one of open, resolved, or detached.",
1811
+ });
1812
+ }
1813
+
1814
+ function validateCommentEntries(
1815
+ value: unknown,
1816
+ path: string,
1817
+ issues: ModelValidationIssue[],
1818
+ ): void {
1819
+ if (!Array.isArray(value)) {
1820
+ issues.push({
1821
+ path,
1822
+ message: "entries must be an array.",
1823
+ });
1824
+ return;
1825
+ }
1826
+
1827
+ value.forEach((entry, index) => {
1828
+ const record = asPlainObject(entry, `${path}[${index}]`, issues);
1829
+ if (!record) {
1830
+ return;
1831
+ }
1832
+ expectString(record.entryId, `${path}[${index}].entryId`, issues);
1833
+ expectString(record.authorId, `${path}[${index}].authorId`, issues);
1834
+ expectStringAllowEmpty(record.body, `${path}[${index}].body`, issues);
1835
+ expectIso8601UtcTimestamp(record.createdAt, `${path}[${index}].createdAt`, issues);
1836
+ if (record.metadata !== undefined) {
1837
+ validateCommentEntryMetadata(record.metadata, `${path}[${index}].metadata`, issues);
1838
+ }
1839
+ });
1840
+ }
1841
+
1842
+ function validateCommentEntryMetadata(
1843
+ value: unknown,
1844
+ path: string,
1845
+ issues: ModelValidationIssue[],
1846
+ ): void {
1847
+ const record = asPlainObject(value, path, issues);
1848
+ if (!record) {
1849
+ return;
1850
+ }
1851
+
1852
+ for (const field of [
1853
+ "ooxmlCommentId",
1854
+ "paraId",
1855
+ "parentParaId",
1856
+ "durableId",
1857
+ "initials",
1858
+ ] as const) {
1859
+ if (record[field] !== undefined) {
1860
+ expectString(record[field], `${path}.${field}`, issues);
1861
+ }
1862
+ }
1863
+ }
1864
+
1865
+ function validateCommentResolution(
1866
+ value: unknown,
1867
+ path: string,
1868
+ issues: ModelValidationIssue[],
1869
+ ): void {
1870
+ const record = asPlainObject(value, path, issues);
1871
+ if (!record) {
1872
+ return;
1873
+ }
1874
+
1875
+ expectIso8601UtcTimestamp(record.resolvedAt, `${path}.resolvedAt`, issues);
1876
+ expectString(record.resolvedBy, `${path}.resolvedBy`, issues);
1877
+ }
1878
+
1879
+ function validateCommentThreadMetadata(
1880
+ value: unknown,
1881
+ path: string,
1882
+ issues: ModelValidationIssue[],
1883
+ ): void {
1884
+ const record = asPlainObject(value, path, issues);
1885
+ if (!record) {
1886
+ return;
1887
+ }
1888
+
1889
+ if (
1890
+ record.source !== undefined &&
1891
+ record.source !== "runtime" &&
1892
+ record.source !== "import"
1893
+ ) {
1894
+ issues.push({
1895
+ path: `${path}.source`,
1896
+ message: "source must be either runtime or import.",
1897
+ });
1898
+ }
1899
+ if (record.rootOoxmlCommentId !== undefined) {
1900
+ expectString(record.rootOoxmlCommentId, `${path}.rootOoxmlCommentId`, issues);
1901
+ }
1902
+ if (record.rootParaId !== undefined) {
1903
+ expectString(record.rootParaId, `${path}.rootParaId`, issues);
1904
+ }
1905
+ }
1906
+
1907
+ function validateRevisionKind(
1908
+ value: unknown,
1909
+ path: string,
1910
+ issues: ModelValidationIssue[],
1911
+ ): void {
1912
+ if (
1913
+ value === "insertion" ||
1914
+ value === "deletion" ||
1915
+ value === "formatting" ||
1916
+ value === "move" ||
1917
+ value === "property-change"
1918
+ ) {
1919
+ return;
1920
+ }
1921
+ issues.push({
1922
+ path,
1923
+ message: "kind must be insertion, deletion, formatting, move, or property-change.",
1924
+ });
1925
+ }
1926
+
1927
+ function validateRevisionStatus(
1928
+ value: unknown,
1929
+ path: string,
1930
+ issues: ModelValidationIssue[],
1931
+ ): void {
1932
+ if (
1933
+ value === "open" ||
1934
+ value === "accepted" ||
1935
+ value === "rejected" ||
1936
+ value === "detached"
1937
+ ) {
1938
+ return;
1939
+ }
1940
+ issues.push({
1941
+ path,
1942
+ message: "status must be one of open, accepted, rejected, or detached.",
1943
+ });
1944
+ }
1945
+
1946
+ function validateWarningIds(
1947
+ value: unknown,
1948
+ path: string,
1949
+ issues: ModelValidationIssue[],
1950
+ ): void {
1951
+ if (!Array.isArray(value)) {
1952
+ issues.push({
1953
+ path,
1954
+ message: "warningIds must be an array.",
1955
+ });
1956
+ return;
1957
+ }
1958
+
1959
+ value.forEach((warningId, index) =>
1960
+ expectDomainString(warningId, "warningId", `${path}[${index}]`, issues),
1961
+ );
1962
+ }
1963
+
1964
+ function validateRevisionMetadata(
1965
+ value: unknown,
1966
+ path: string,
1967
+ issues: ModelValidationIssue[],
1968
+ ): void {
1969
+ const record = asPlainObject(value, path, issues);
1970
+ if (!record) {
1971
+ return;
1972
+ }
1973
+
1974
+ if (
1975
+ record.source !== undefined &&
1976
+ record.source !== "runtime" &&
1977
+ record.source !== "import"
1978
+ ) {
1979
+ issues.push({
1980
+ path: `${path}.source`,
1981
+ message: "source must be either runtime or import.",
1982
+ });
1983
+ }
1984
+ for (const field of [
1985
+ "preserveOnlyReason",
1986
+ "suggestionId",
1987
+ "semanticKind",
1988
+ "predecessorSuggestionId",
1989
+ "importedRevisionForm",
1990
+ "originalRevisionType",
1991
+ "ooxmlRevisionId",
1992
+ ] as const) {
1993
+ if (record[field] !== undefined) {
1994
+ expectString(record[field], `${path}.${field}`, issues);
1995
+ }
1996
+ }
1997
+
1998
+ if (record.linkedRevisionIds !== undefined) {
1999
+ if (!Array.isArray(record.linkedRevisionIds)) {
2000
+ issues.push({
2001
+ path: `${path}.linkedRevisionIds`,
2002
+ message: "linkedRevisionIds must be an array of strings.",
2003
+ });
2004
+ } else {
2005
+ for (const [index, value] of record.linkedRevisionIds.entries()) {
2006
+ expectString(value, `${path}.linkedRevisionIds.${index}`, issues);
2007
+ }
2008
+ }
2009
+ }
2010
+
2011
+ if (record.propertyChangeData !== undefined) {
2012
+ const pcd = asPlainObject(record.propertyChangeData, `${path}.propertyChangeData`, issues);
2013
+ if (pcd) {
2014
+ expectString(pcd.xmlTag, `${path}.propertyChangeData.xmlTag`, issues);
2015
+ expectString(pcd.beforeXml, `${path}.propertyChangeData.beforeXml`, issues);
2016
+ }
2017
+ }
2018
+
2019
+ if (record.moveData !== undefined) {
2020
+ const md = asPlainObject(record.moveData, `${path}.moveData`, issues);
2021
+ if (md) {
2022
+ expectString(md.moveId, `${path}.moveData.moveId`, issues);
2023
+ expectString(md.direction, `${path}.moveData.direction`, issues);
2024
+ }
2025
+ }
2026
+ }
2027
+
2028
+ function validatePreservationStore(
2029
+ value: unknown,
2030
+ path: string,
2031
+ issues: ModelValidationIssue[],
2032
+ ): void {
2033
+ const record = asPlainObject(value, path, issues);
2034
+ if (!record) {
2035
+ return;
2036
+ }
2037
+
2038
+ const opaqueFragments = asPlainObject(
2039
+ record.opaqueFragments,
2040
+ `${path}.opaqueFragments`,
2041
+ issues,
2042
+ );
2043
+ if (opaqueFragments) {
2044
+ for (const [fragmentId, fragment] of Object.entries(opaqueFragments)) {
2045
+ expectDomainString(fragmentId, "fragmentId", `${path}.opaqueFragments.${fragmentId}`, issues);
2046
+ const fragmentRecord = asPlainObject(
2047
+ fragment,
2048
+ `${path}.opaqueFragments.${fragmentId}`,
2049
+ issues,
2050
+ );
2051
+ if (!fragmentRecord) {
2052
+ continue;
2053
+ }
2054
+ if (fragmentRecord.fragmentId !== fragmentId) {
2055
+ issues.push({
2056
+ path: `${path}.opaqueFragments.${fragmentId}.fragmentId`,
2057
+ message: "fragmentId must match the map key.",
2058
+ });
2059
+ }
2060
+ validateRange(
2061
+ fragmentRecord.lastKnownRange,
2062
+ `${path}.opaqueFragments.${fragmentId}.lastKnownRange`,
2063
+ issues,
2064
+ );
2065
+ expectDomainString(
2066
+ fragmentRecord.warningId,
2067
+ "warningId",
2068
+ `${path}.opaqueFragments.${fragmentId}.warningId`,
2069
+ issues,
2070
+ );
2071
+ if (fragmentRecord.packagePartName !== undefined) {
2072
+ expectDomainString(
2073
+ fragmentRecord.packagePartName,
2074
+ "packagePartName",
2075
+ `${path}.opaqueFragments.${fragmentId}.packagePartName`,
2076
+ issues,
2077
+ );
2078
+ }
2079
+ if (fragmentRecord.relationshipId !== undefined) {
2080
+ expectDomainString(
2081
+ fragmentRecord.relationshipId,
2082
+ "relationshipId",
2083
+ `${path}.opaqueFragments.${fragmentId}.relationshipId`,
2084
+ issues,
2085
+ );
2086
+ }
2087
+ }
2088
+ }
2089
+
2090
+ const packageParts = asPlainObject(record.packageParts, `${path}.packageParts`, issues);
2091
+ if (packageParts) {
2092
+ for (const [packagePartName, packagePart] of Object.entries(packageParts)) {
2093
+ expectDomainString(
2094
+ packagePartName,
2095
+ "packagePartName",
2096
+ `${path}.packageParts.${packagePartName}`,
2097
+ issues,
2098
+ );
2099
+ const packagePartRecord = asPlainObject(
2100
+ packagePart,
2101
+ `${path}.packageParts.${packagePartName}`,
2102
+ issues,
2103
+ );
2104
+ if (!packagePartRecord) {
2105
+ continue;
2106
+ }
2107
+ if (packagePartRecord.packagePartName !== packagePartName) {
2108
+ issues.push({
2109
+ path: `${path}.packageParts.${packagePartName}.packagePartName`,
2110
+ message: "packagePartName must match the map key.",
2111
+ });
2112
+ }
2113
+ if (Array.isArray(packagePartRecord.relationshipIds)) {
2114
+ packagePartRecord.relationshipIds.forEach((relationshipId, index) =>
2115
+ expectDomainString(
2116
+ relationshipId,
2117
+ "relationshipId",
2118
+ `${path}.packageParts.${packagePartName}.relationshipIds[${index}]`,
2119
+ issues,
2120
+ ),
2121
+ );
2122
+ }
2123
+ }
2124
+ }
2125
+ }
2126
+
2127
+ function validateDiagnosticStore(
2128
+ value: unknown,
2129
+ path: string,
2130
+ issues: ModelValidationIssue[],
2131
+ ): void {
2132
+ const record = asPlainObject(value, path, issues);
2133
+ if (!record) {
2134
+ return;
2135
+ }
2136
+
2137
+ if (Array.isArray(record.warnings)) {
2138
+ record.warnings.forEach((warning, index) => {
2139
+ const warningRecord = asPlainObject(warning, `${path}.warnings[${index}]`, issues);
2140
+ if (!warningRecord) {
2141
+ return;
2142
+ }
2143
+ expectDomainString(
2144
+ warningRecord.diagnosticId,
2145
+ "diagnosticId",
2146
+ `${path}.warnings[${index}].diagnosticId`,
2147
+ issues,
2148
+ );
2149
+ expectDomainString(
2150
+ warningRecord.warningId,
2151
+ "warningId",
2152
+ `${path}.warnings[${index}].warningId`,
2153
+ issues,
2154
+ );
2155
+ });
2156
+ }
2157
+
2158
+ if (Array.isArray(record.errors)) {
2159
+ record.errors.forEach((error, index) => {
2160
+ const errorRecord = asPlainObject(error, `${path}.errors[${index}]`, issues);
2161
+ if (!errorRecord) {
2162
+ return;
2163
+ }
2164
+ expectDomainString(
2165
+ errorRecord.diagnosticId,
2166
+ "diagnosticId",
2167
+ `${path}.errors[${index}].diagnosticId`,
2168
+ issues,
2169
+ );
2170
+ });
2171
+ }
2172
+ }
2173
+
2174
+ function validateAnchor(
2175
+ value: unknown,
2176
+ path: string,
2177
+ issues: ModelValidationIssue[],
2178
+ ): void {
2179
+ const record = asPlainObject(value, path, issues);
2180
+ if (!record) {
2181
+ return;
2182
+ }
2183
+
2184
+ const kind = expectString(record.kind, `${path}.kind`, issues);
2185
+ if (!kind) {
2186
+ return;
2187
+ }
2188
+
2189
+ if (kind === "range") {
2190
+ validateRange(record.range, `${path}.range`, issues);
2191
+ validateBoundaryAssoc(record.assoc, `${path}.assoc`, issues);
2192
+ } else if (kind === "node") {
2193
+ if (typeof record.at !== "number") {
2194
+ issues.push({
2195
+ path: `${path}.at`,
2196
+ message: "node anchors must contain a numeric at value.",
2197
+ });
2198
+ }
2199
+ if (record.assoc !== -1 && record.assoc !== 1) {
2200
+ issues.push({
2201
+ path: `${path}.assoc`,
2202
+ message: "node anchor assoc must be -1 or 1.",
2203
+ });
2204
+ }
2205
+ } else if (kind === "detached") {
2206
+ validateRange(record.lastKnownRange, `${path}.lastKnownRange`, issues);
2207
+ if (
2208
+ record.reason !== "deleted" &&
2209
+ record.reason !== "invalidatedByStructureChange" &&
2210
+ record.reason !== "importAmbiguity"
2211
+ ) {
2212
+ issues.push({
2213
+ path: `${path}.reason`,
2214
+ message: "detached anchor reason must be deleted, invalidatedByStructureChange, or importAmbiguity.",
2215
+ });
2216
+ }
2217
+ } else {
2218
+ issues.push({
2219
+ path: `${path}.kind`,
2220
+ message: "anchor kind must be range, node, or detached.",
2221
+ });
2222
+ }
2223
+ }
2224
+
2225
+ function validateBoundaryAssoc(
2226
+ value: unknown,
2227
+ path: string,
2228
+ issues: ModelValidationIssue[],
2229
+ ): void {
2230
+ const record = asPlainObject(value, path, issues);
2231
+ if (!record) {
2232
+ return;
2233
+ }
2234
+
2235
+ if (record.start !== -1 && record.start !== 1) {
2236
+ issues.push({
2237
+ path: `${path}.start`,
2238
+ message: "assoc.start must be -1 or 1.",
2239
+ });
2240
+ }
2241
+ if (record.end !== -1 && record.end !== 1) {
2242
+ issues.push({
2243
+ path: `${path}.end`,
2244
+ message: "assoc.end must be -1 or 1.",
2245
+ });
2246
+ }
2247
+ }
2248
+
2249
+ function validateDocumentReferences(
2250
+ record: Record<string, unknown>,
2251
+ issues: ModelValidationIssue[],
2252
+ ): void {
2253
+ const paragraphStyleIds = new Set(
2254
+ Object.keys(asPlainObject(asPlainObject(record.styles, "$.styles", [])?.paragraphs, "$.styles.paragraphs", []) ?? {}),
2255
+ );
2256
+ const numberingInstanceIds = new Set(
2257
+ Object.keys(asPlainObject(asPlainObject(record.numbering, "$.numbering", [])?.instances, "$.numbering.instances", []) ?? {}),
2258
+ );
2259
+ const abstractNumberingIds = new Set(
2260
+ Object.keys(asPlainObject(asPlainObject(record.numbering, "$.numbering", [])?.abstractDefinitions, "$.numbering.abstractDefinitions", []) ?? {}),
2261
+ );
2262
+ const mediaIds = new Set(
2263
+ Object.keys(asPlainObject(asPlainObject(record.media, "$.media", [])?.items, "$.media.items", []) ?? {}),
2264
+ );
2265
+ const warningIds = new Set(
2266
+ (Array.isArray(asPlainObject(record.diagnostics, "$.diagnostics", [])?.warnings)
2267
+ ? (asPlainObject(record.diagnostics, "$.diagnostics", [])?.warnings as Array<Record<string, unknown>>)
2268
+ : []
2269
+ ).flatMap((warning) => typeof warning.warningId === "string" ? [warning.warningId] : []),
2270
+ );
2271
+ const fragmentIds = new Set(
2272
+ Object.keys(asPlainObject(asPlainObject(record.preservation, "$.preservation", [])?.opaqueFragments, "$.preservation.opaqueFragments", []) ?? {}),
2273
+ );
2274
+ const noteIds = collectNoteIds(record);
2275
+
2276
+ const numberingInstances = asPlainObject(
2277
+ asPlainObject(record.numbering, "$.numbering", [])?.instances,
2278
+ "$.numbering.instances",
2279
+ [],
2280
+ );
2281
+ if (numberingInstances) {
2282
+ for (const [instanceId, instance] of Object.entries(numberingInstances)) {
2283
+ const instanceRecord = asPlainObject(instance, `$.numbering.instances.${instanceId}`, []);
2284
+ if (
2285
+ instanceRecord &&
2286
+ typeof instanceRecord.abstractNumberingId === "string" &&
2287
+ !abstractNumberingIds.has(instanceRecord.abstractNumberingId)
2288
+ ) {
2289
+ issues.push({
2290
+ path: `$.numbering.instances.${instanceId}.abstractNumberingId`,
2291
+ message: "abstractNumberingId must reference an existing abstract numbering definition.",
2292
+ });
2293
+ }
2294
+ }
2295
+ }
2296
+
2297
+ validateDocumentNodeReferences(record.content, "$.content", issues, {
2298
+ paragraphStyleIds,
2299
+ numberingInstanceIds,
2300
+ mediaIds,
2301
+ warningIds,
2302
+ fragmentIds,
2303
+ noteIds,
2304
+ });
2305
+ validateSubPartReferences(record.subParts, "$.subParts", issues, {
2306
+ paragraphStyleIds,
2307
+ numberingInstanceIds,
2308
+ mediaIds,
2309
+ warningIds,
2310
+ fragmentIds,
2311
+ noteIds,
2312
+ });
2313
+ validateReviewReferences(record.review, "$.review", issues, warningIds);
2314
+ }
2315
+
2316
+ function validateDocumentNodeReferences(
2317
+ value: unknown,
2318
+ path: string,
2319
+ issues: ModelValidationIssue[],
2320
+ references: {
2321
+ paragraphStyleIds: ReadonlySet<string>;
2322
+ numberingInstanceIds: ReadonlySet<string>;
2323
+ mediaIds: ReadonlySet<string>;
2324
+ warningIds: ReadonlySet<string>;
2325
+ fragmentIds: ReadonlySet<string>;
2326
+ noteIds: ReadonlySet<string>;
2327
+ },
2328
+ ): void {
2329
+ const record = asPlainObject(value, path, issues);
2330
+ if (!record) {
2331
+ return;
2332
+ }
2333
+
2334
+ const type = typeof record.type === "string" ? record.type : undefined;
2335
+ if (type === "paragraph") {
2336
+ if (
2337
+ typeof record.styleId === "string" &&
2338
+ !references.paragraphStyleIds.has(record.styleId)
2339
+ ) {
2340
+ issues.push({
2341
+ path: `${path}.styleId`,
2342
+ message: "styleId must reference an existing paragraph style definition.",
2343
+ });
2344
+ }
2345
+ const numbering = asPlainObject(record.numbering, `${path}.numbering`, []);
2346
+ if (
2347
+ numbering &&
2348
+ typeof numbering.numberingInstanceId === "string" &&
2349
+ !references.numberingInstanceIds.has(numbering.numberingInstanceId)
2350
+ ) {
2351
+ issues.push({
2352
+ path: `${path}.numbering.numberingInstanceId`,
2353
+ message: "numberingInstanceId must reference an existing numbering instance.",
2354
+ });
2355
+ }
2356
+ } else if (type === "image") {
2357
+ if (typeof record.mediaId === "string" && !references.mediaIds.has(record.mediaId)) {
2358
+ issues.push({
2359
+ path: `${path}.mediaId`,
2360
+ message: "mediaId must reference an existing media catalog item.",
2361
+ });
2362
+ }
2363
+ } else if (type === "opaque_inline" || type === "opaque_block") {
2364
+ if (typeof record.fragmentId === "string" && !references.fragmentIds.has(record.fragmentId)) {
2365
+ issues.push({
2366
+ path: `${path}.fragmentId`,
2367
+ message: "fragmentId must reference an existing opaque fragment.",
2368
+ });
2369
+ }
2370
+ if (typeof record.warningId === "string" && !references.warningIds.has(record.warningId)) {
2371
+ issues.push({
2372
+ path: `${path}.warningId`,
2373
+ message: "warningId must reference an existing diagnostic warning.",
2374
+ });
2375
+ }
2376
+ } else if (type === "footnote_ref") {
2377
+ if (typeof record.noteId === "string" && !references.noteIds.has(`${record.noteKind}:${record.noteId}`)) {
2378
+ issues.push({
2379
+ path: `${path}.noteId`,
2380
+ message: "noteId must reference an existing footnote or endnote definition.",
2381
+ });
2382
+ }
2383
+ }
2384
+
2385
+ if (Array.isArray(record.children)) {
2386
+ record.children.forEach((child, index) =>
2387
+ validateDocumentNodeReferences(child, `${path}.children[${index}]`, issues, references),
2388
+ );
2389
+ }
2390
+ if (Array.isArray(record.rows)) {
2391
+ record.rows.forEach((row, index) =>
2392
+ validateDocumentNodeReferences(row, `${path}.rows[${index}]`, issues, references),
2393
+ );
2394
+ }
2395
+ if (Array.isArray(record.cells)) {
2396
+ record.cells.forEach((cell, index) =>
2397
+ validateDocumentNodeReferences(cell, `${path}.cells[${index}]`, issues, references),
2398
+ );
2399
+ }
2400
+ }
2401
+
2402
+ function validateReviewReferences(
2403
+ value: unknown,
2404
+ path: string,
2405
+ issues: ModelValidationIssue[],
2406
+ warningIds: ReadonlySet<string>,
2407
+ ): void {
2408
+ const record = asPlainObject(value, path, issues);
2409
+ if (!record) {
2410
+ return;
2411
+ }
2412
+
2413
+ const comments = asPlainObject(record.comments, `${path}.comments`, []);
2414
+ if (comments) {
2415
+ for (const [commentId, thread] of Object.entries(comments)) {
2416
+ const threadRecord = asPlainObject(thread, `${path}.comments.${commentId}`, []);
2417
+ if (!threadRecord || !Array.isArray(threadRecord.warningIds)) {
2418
+ continue;
2419
+ }
2420
+ threadRecord.warningIds.forEach((warningId, index) => {
2421
+ if (typeof warningId === "string" && !warningIds.has(warningId)) {
2422
+ issues.push({
2423
+ path: `${path}.comments.${commentId}.warningIds[${index}]`,
2424
+ message: "warningIds must reference existing diagnostic warnings.",
2425
+ });
2426
+ }
2427
+ });
2428
+ }
2429
+ }
2430
+
2431
+ const revisions = asPlainObject(record.revisions, `${path}.revisions`, []);
2432
+ if (revisions) {
2433
+ for (const [revisionId, revision] of Object.entries(revisions)) {
2434
+ const revisionRecord = asPlainObject(revision, `${path}.revisions.${revisionId}`, []);
2435
+ if (!revisionRecord || !Array.isArray(revisionRecord.warningIds)) {
2436
+ continue;
2437
+ }
2438
+ revisionRecord.warningIds.forEach((warningId, index) => {
2439
+ if (typeof warningId === "string" && !warningIds.has(warningId)) {
2440
+ issues.push({
2441
+ path: `${path}.revisions.${revisionId}.warningIds[${index}]`,
2442
+ message: "warningIds must reference existing diagnostic warnings.",
2443
+ });
2444
+ }
2445
+ });
2446
+ }
2447
+ }
2448
+ }
2449
+
2450
+ function validateSubPartReferences(
2451
+ value: unknown,
2452
+ path: string,
2453
+ issues: ModelValidationIssue[],
2454
+ references: {
2455
+ paragraphStyleIds: ReadonlySet<string>;
2456
+ numberingInstanceIds: ReadonlySet<string>;
2457
+ mediaIds: ReadonlySet<string>;
2458
+ warningIds: ReadonlySet<string>;
2459
+ fragmentIds: ReadonlySet<string>;
2460
+ noteIds: ReadonlySet<string>;
2461
+ },
2462
+ ): void {
2463
+ if (value === undefined) {
2464
+ return;
2465
+ }
2466
+ const record = asPlainObject(value, path, issues);
2467
+ if (!record) {
2468
+ return;
2469
+ }
2470
+
2471
+ const headers = Array.isArray(record.headers) ? record.headers : [];
2472
+ headers.forEach((header, index) => {
2473
+ const headerRecord = asPlainObject(header, `${path}.headers[${index}]`, []);
2474
+ if (!headerRecord || !Array.isArray(headerRecord.blocks)) {
2475
+ return;
2476
+ }
2477
+ headerRecord.blocks.forEach((block, blockIndex) =>
2478
+ validateDocumentNodeReferences(block, `${path}.headers[${index}].blocks[${blockIndex}]`, issues, references),
2479
+ );
2480
+ });
2481
+
2482
+ const footers = Array.isArray(record.footers) ? record.footers : [];
2483
+ footers.forEach((footer, index) => {
2484
+ const footerRecord = asPlainObject(footer, `${path}.footers[${index}]`, []);
2485
+ if (!footerRecord || !Array.isArray(footerRecord.blocks)) {
2486
+ return;
2487
+ }
2488
+ footerRecord.blocks.forEach((block, blockIndex) =>
2489
+ validateDocumentNodeReferences(block, `${path}.footers[${index}].blocks[${blockIndex}]`, issues, references),
2490
+ );
2491
+ });
2492
+
2493
+ if (record.footnoteCollection !== undefined) {
2494
+ const footnoteCollection = asPlainObject(record.footnoteCollection, `${path}.footnoteCollection`, []);
2495
+ const footnotes = asPlainObject(footnoteCollection?.footnotes, `${path}.footnoteCollection.footnotes`, []);
2496
+ if (footnotes) {
2497
+ for (const [noteId, note] of Object.entries(footnotes)) {
2498
+ const noteRecord = asPlainObject(note, `${path}.footnoteCollection.footnotes.${noteId}`, []);
2499
+ if (!noteRecord || !Array.isArray(noteRecord.blocks)) {
2500
+ continue;
2501
+ }
2502
+ noteRecord.blocks.forEach((block, blockIndex) =>
2503
+ validateDocumentNodeReferences(
2504
+ block,
2505
+ `${path}.footnoteCollection.footnotes.${noteId}.blocks[${blockIndex}]`,
2506
+ issues,
2507
+ references,
2508
+ ),
2509
+ );
2510
+ }
2511
+ }
2512
+
2513
+ const endnotes = asPlainObject(footnoteCollection?.endnotes, `${path}.footnoteCollection.endnotes`, []);
2514
+ if (endnotes) {
2515
+ for (const [noteId, note] of Object.entries(endnotes)) {
2516
+ const noteRecord = asPlainObject(note, `${path}.footnoteCollection.endnotes.${noteId}`, []);
2517
+ if (!noteRecord || !Array.isArray(noteRecord.blocks)) {
2518
+ continue;
2519
+ }
2520
+ noteRecord.blocks.forEach((block, blockIndex) =>
2521
+ validateDocumentNodeReferences(
2522
+ block,
2523
+ `${path}.footnoteCollection.endnotes.${noteId}.blocks[${blockIndex}]`,
2524
+ issues,
2525
+ references,
2526
+ ),
2527
+ );
2528
+ }
2529
+ }
2530
+ }
2531
+ }
2532
+
2533
+ function collectNoteIds(record: Record<string, unknown>): Set<string> {
2534
+ const noteIds = new Set<string>();
2535
+ const subParts = asPlainObject(record.subParts, "$.subParts", []);
2536
+ const footnoteCollection = asPlainObject(subParts?.footnoteCollection, "$.subParts.footnoteCollection", []);
2537
+ const footnotes = asPlainObject(footnoteCollection?.footnotes, "$.subParts.footnoteCollection.footnotes", []);
2538
+ if (footnotes) {
2539
+ Object.keys(footnotes).forEach((noteId) => noteIds.add(`footnote:${noteId}`));
2540
+ }
2541
+ const endnotes = asPlainObject(footnoteCollection?.endnotes, "$.subParts.footnoteCollection.endnotes", []);
2542
+ if (endnotes) {
2543
+ Object.keys(endnotes).forEach((noteId) => noteIds.add(`endnote:${noteId}`));
2544
+ }
2545
+ return noteIds;
2546
+ }
2547
+
2548
+ function validateRange(
2549
+ value: unknown,
2550
+ path: string,
2551
+ issues: ModelValidationIssue[],
2552
+ ): void {
2553
+ const record = asPlainObject(value, path, issues);
2554
+ if (!record) {
2555
+ return;
2556
+ }
2557
+
2558
+ if (typeof record.from !== "number" || typeof record.to !== "number") {
2559
+ issues.push({
2560
+ path,
2561
+ message: "Range must contain numeric from and to values.",
2562
+ });
2563
+ }
2564
+ }
2565
+
2566
+ function validateExactObjectKeys(
2567
+ record: Record<string, unknown>,
2568
+ expectedKeys: readonly string[],
2569
+ path: string,
2570
+ issues: ModelValidationIssue[],
2571
+ allowedOptionalKeys?: readonly string[],
2572
+ ): void {
2573
+ const actualKeys = new Set(Object.keys(record));
2574
+ const expected = new Set(expectedKeys);
2575
+ const optional = new Set(allowedOptionalKeys ?? []);
2576
+
2577
+ for (const expectedKey of expectedKeys) {
2578
+ if (!actualKeys.has(expectedKey)) {
2579
+ issues.push({
2580
+ path,
2581
+ message: `Missing required key ${JSON.stringify(expectedKey)}.`,
2582
+ });
2583
+ }
2584
+ }
2585
+
2586
+ for (const actualKey of actualKeys) {
2587
+ if (!expected.has(actualKey) && !optional.has(actualKey)) {
2588
+ issues.push({
2589
+ path: `${path}.${actualKey}`,
2590
+ message:
2591
+ "Unexpected canonical document key. Render/UI state is not part of the canonical envelope.",
2592
+ });
2593
+ }
2594
+ }
2595
+ }
2596
+
2597
+ function validateSubPartsCatalog(
2598
+ value: unknown,
2599
+ path: string,
2600
+ issues: ModelValidationIssue[],
2601
+ ): void {
2602
+ if (value === undefined) {
2603
+ return;
2604
+ }
2605
+ const record = asPlainObject(value, path, issues);
2606
+ if (!record) {
2607
+ return;
2608
+ }
2609
+
2610
+ if (record.headers !== undefined) {
2611
+ if (!Array.isArray(record.headers)) {
2612
+ issues.push({ path: `${path}.headers`, message: "headers must be an array." });
2613
+ } else {
2614
+ record.headers.forEach((header, index) => {
2615
+ const headerRecord = asPlainObject(header, `${path}.headers[${index}]`, issues);
2616
+ if (headerRecord) {
2617
+ expectString(headerRecord.variant, `${path}.headers[${index}].variant`, issues);
2618
+ expectString(headerRecord.partPath, `${path}.headers[${index}].partPath`, issues);
2619
+ expectString(headerRecord.relationshipId, `${path}.headers[${index}].relationshipId`, issues);
2620
+ if (!Array.isArray(headerRecord.blocks)) {
2621
+ issues.push({ path: `${path}.headers[${index}].blocks`, message: "blocks must be an array." });
2622
+ } else {
2623
+ headerRecord.blocks.forEach((block, blockIndex) =>
2624
+ validateDocumentNode(block, `${path}.headers[${index}].blocks[${blockIndex}]`, issues),
2625
+ );
2626
+ }
2627
+ }
2628
+ });
2629
+ }
2630
+ }
2631
+
2632
+ if (record.footers !== undefined) {
2633
+ if (!Array.isArray(record.footers)) {
2634
+ issues.push({ path: `${path}.footers`, message: "footers must be an array." });
2635
+ } else {
2636
+ record.footers.forEach((footer, index) => {
2637
+ const footerRecord = asPlainObject(footer, `${path}.footers[${index}]`, issues);
2638
+ if (footerRecord) {
2639
+ expectString(footerRecord.variant, `${path}.footers[${index}].variant`, issues);
2640
+ expectString(footerRecord.partPath, `${path}.footers[${index}].partPath`, issues);
2641
+ expectString(footerRecord.relationshipId, `${path}.footers[${index}].relationshipId`, issues);
2642
+ if (!Array.isArray(footerRecord.blocks)) {
2643
+ issues.push({ path: `${path}.footers[${index}].blocks`, message: "blocks must be an array." });
2644
+ } else {
2645
+ footerRecord.blocks.forEach((block, blockIndex) =>
2646
+ validateDocumentNode(block, `${path}.footers[${index}].blocks[${blockIndex}]`, issues),
2647
+ );
2648
+ }
2649
+ }
2650
+ });
2651
+ }
2652
+ }
2653
+
2654
+ if (record.footnoteCollection !== undefined) {
2655
+ const footnoteCollection = asPlainObject(record.footnoteCollection, `${path}.footnoteCollection`, issues);
2656
+ if (footnoteCollection) {
2657
+ validateSubPartNoteMap(footnoteCollection.footnotes, "footnote", `${path}.footnoteCollection.footnotes`, issues);
2658
+ validateSubPartNoteMap(footnoteCollection.endnotes, "endnote", `${path}.footnoteCollection.endnotes`, issues);
2659
+ }
2660
+ }
2661
+ }
2662
+
2663
+ function validateSubPartNoteMap(
2664
+ value: unknown,
2665
+ expectedKind: "footnote" | "endnote",
2666
+ path: string,
2667
+ issues: ModelValidationIssue[],
2668
+ ): void {
2669
+ const record = asPlainObject(value, path, issues);
2670
+ if (!record) {
2671
+ return;
2672
+ }
2673
+
2674
+ for (const [noteId, note] of Object.entries(record)) {
2675
+ const noteRecord = asPlainObject(note, `${path}.${noteId}`, issues);
2676
+ if (!noteRecord) {
2677
+ continue;
2678
+ }
2679
+ expectString(noteRecord.noteId, `${path}.${noteId}.noteId`, issues);
2680
+ if (noteRecord.noteId !== noteId) {
2681
+ issues.push({
2682
+ path: `${path}.${noteId}.noteId`,
2683
+ message: "noteId must match the map key.",
2684
+ });
2685
+ }
2686
+ if (noteRecord.kind !== expectedKind) {
2687
+ issues.push({
2688
+ path: `${path}.${noteId}.kind`,
2689
+ message: `kind must be ${expectedKind}.`,
2690
+ });
2691
+ }
2692
+ if (!Array.isArray(noteRecord.blocks)) {
2693
+ issues.push({ path: `${path}.${noteId}.blocks`, message: "blocks must be an array." });
2694
+ } else {
2695
+ noteRecord.blocks.forEach((block, blockIndex) =>
2696
+ validateDocumentNode(block, `${path}.${noteId}.blocks[${blockIndex}]`, issues),
2697
+ );
2698
+ }
2699
+ }
2700
+ }
2701
+
2702
+ function expectDomainString(
2703
+ value: unknown,
2704
+ domain: StableIdDomain,
2705
+ path: string,
2706
+ issues: ModelValidationIssue[],
2707
+ ): string | null {
2708
+ const stableId = expectString(value, path, issues);
2709
+ if (!stableId) {
2710
+ return null;
2711
+ }
2712
+
2713
+ if (!ID_PATTERNS[domain].test(stableId)) {
2714
+ issues.push({
2715
+ path,
2716
+ message: `Expected a valid ${domain}.`,
2717
+ });
2718
+ return null;
2719
+ }
2720
+
2721
+ return stableId;
2722
+ }