hyper-html-api 0.14.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (415) hide show
  1. package/conformance/MANIFEST.json +2 -2
  2. package/conformance/cases/codec-form-nested.error.json +5 -0
  3. package/conformance/cases/codec-form-nested.html +4 -0
  4. package/conformance/cases/codec-form-nested.meta +6 -0
  5. package/conformance/cases/codec-form-nested.parsed.json +5 -0
  6. package/conformance/cases/codec-form-star.error.json +5 -0
  7. package/conformance/cases/codec-form-star.html +4 -0
  8. package/conformance/cases/codec-form-star.meta +6 -0
  9. package/conformance/cases/codec-form-star.parsed.json +3 -0
  10. package/conformance/cases/codec-form-under-v1.error.json +5 -0
  11. package/conformance/cases/codec-form-under-v1.html +4 -0
  12. package/conformance/cases/codec-form-under-v1.meta +6 -0
  13. package/conformance/cases/codec-form-under-v1.parsed.json +3 -0
  14. package/conformance/cases/codec-unknown-name.error.json +5 -0
  15. package/conformance/cases/codec-unknown-name.html +4 -0
  16. package/conformance/cases/codec-unknown-name.meta +6 -0
  17. package/conformance/cases/codec-unknown-name.parsed.json +3 -0
  18. package/conformance/cases/tag-version-2-plain.expected.json +7 -0
  19. package/conformance/cases/tag-version-2-plain.html +5 -0
  20. package/conformance/cases/tag-version-2-plain.meta +6 -0
  21. package/conformance/cases/tag-version-2-plain.parsed.json +4 -0
  22. package/conformance/cases/tag-wrong-version.error.json +1 -1
  23. package/conformance/cases/tag-wrong-version.html +1 -1
  24. package/conformance/sheet/body-data-write.body.json +3 -0
  25. package/conformance/sheet/body-data-write.data.json +3 -0
  26. package/conformance/sheet/body-data-write.html +25 -0
  27. package/conformance/sheet/body-data-write.meta +5 -0
  28. package/conformance/sheet/body-patch-key-in-rules.body.json +3 -0
  29. package/conformance/sheet/body-patch-key-in-rules.data.json +13 -0
  30. package/conformance/sheet/body-patch-key-in-rules.html +18 -0
  31. package/conformance/sheet/body-patch-key-in-rules.meta +5 -0
  32. package/conformance/sheet/body-patch-not-array.body.json +3 -0
  33. package/conformance/sheet/body-patch-not-array.data.json +3 -0
  34. package/conformance/sheet/body-patch-not-array.html +25 -0
  35. package/conformance/sheet/body-patch-not-array.meta +5 -0
  36. package/conformance/sheet/body-patch-on-sheet.body.json +3 -0
  37. package/conformance/sheet/body-patch-on-sheet.data.json +14 -0
  38. package/conformance/sheet/body-patch-on-sheet.html +25 -0
  39. package/conformance/sheet/body-patch-on-sheet.meta +5 -0
  40. package/conformance/sheet/body-patch-version-1.body.json +3 -0
  41. package/conformance/sheet/body-patch-version-1.data.json +13 -0
  42. package/conformance/sheet/body-patch-version-1.html +4 -0
  43. package/conformance/sheet/body-patch-version-1.meta +5 -0
  44. package/conformance/sheet/body-patch-with-other-key.body.json +3 -0
  45. package/conformance/sheet/body-patch-with-other-key.data.json +14 -0
  46. package/conformance/sheet/body-patch-with-other-key.html +25 -0
  47. package/conformance/sheet/body-patch-with-other-key.meta +5 -0
  48. package/conformance/sheet/body-patch-without-operation-id.body.json +3 -0
  49. package/conformance/sheet/body-patch-without-operation-id.data.json +13 -0
  50. package/conformance/sheet/body-patch-without-operation-id.html +25 -0
  51. package/conformance/sheet/body-patch-without-operation-id.meta +5 -0
  52. package/conformance/sheet/params-bad-range.error.json +6 -0
  53. package/conformance/sheet/params-bad-range.html +25 -0
  54. package/conformance/sheet/params-bad-range.meta +6 -0
  55. package/conformance/sheet/params-bad-render.error.json +6 -0
  56. package/conformance/sheet/params-bad-render.html +25 -0
  57. package/conformance/sheet/params-bad-render.meta +6 -0
  58. package/conformance/sheet/params-bad-shape.error.json +6 -0
  59. package/conformance/sheet/params-bad-shape.html +25 -0
  60. package/conformance/sheet/params-bad-shape.meta +6 -0
  61. package/conformance/sheet/params-cache-buster.expected.json +128 -0
  62. package/conformance/sheet/params-cache-buster.html +25 -0
  63. package/conformance/sheet/params-cache-buster.meta +6 -0
  64. package/conformance/sheet/params-data-alone.expected.json +128 -0
  65. package/conformance/sheet/params-data-alone.html +25 -0
  66. package/conformance/sheet/params-data-alone.meta +6 -0
  67. package/conformance/sheet/params-empty.error.json +6 -0
  68. package/conformance/sheet/params-empty.html +25 -0
  69. package/conformance/sheet/params-empty.meta +6 -0
  70. package/conformance/sheet/params-index.expected.json +19 -0
  71. package/conformance/sheet/params-index.html +25 -0
  72. package/conformance/sheet/params-index.meta +6 -0
  73. package/conformance/sheet/params-none.expected.json +128 -0
  74. package/conformance/sheet/params-none.html +25 -0
  75. package/conformance/sheet/params-none.meta +6 -0
  76. package/conformance/sheet/params-range.expected.json +17 -0
  77. package/conformance/sheet/params-range.html +25 -0
  78. package/conformance/sheet/params-range.meta +6 -0
  79. package/conformance/sheet/params-render-without-range.error.json +6 -0
  80. package/conformance/sheet/params-render-without-range.html +25 -0
  81. package/conformance/sheet/params-render-without-range.meta +6 -0
  82. package/conformance/sheet/params-repeated.error.json +6 -0
  83. package/conformance/sheet/params-repeated.html +25 -0
  84. package/conformance/sheet/params-repeated.meta +6 -0
  85. package/conformance/sheet/params-unknown-sheet.error.json +6 -0
  86. package/conformance/sheet/params-unknown-sheet.html +25 -0
  87. package/conformance/sheet/params-unknown-sheet.meta +6 -0
  88. package/conformance/sheet/params-with-data.error.json +6 -0
  89. package/conformance/sheet/params-with-data.html +25 -0
  90. package/conformance/sheet/params-with-data.meta +6 -0
  91. package/conformance/sheet/sheet-contract-cells.expected.json +57 -0
  92. package/conformance/sheet/sheet-contract-cells.html +10 -0
  93. package/conformance/sheet/sheet-contract-cells.meta +6 -0
  94. package/conformance/sheet/sheet-contract-formulas.expected.json +29 -0
  95. package/conformance/sheet/sheet-contract-formulas.html +10 -0
  96. package/conformance/sheet/sheet-contract-formulas.meta +6 -0
  97. package/conformance/sheet/sheet-contract-values.expected.json +29 -0
  98. package/conformance/sheet/sheet-contract-values.html +10 -0
  99. package/conformance/sheet/sheet-contract-values.meta +6 -0
  100. package/conformance/sheet/sheet-contract-whole.expected.json +59 -0
  101. package/conformance/sheet/sheet-contract-whole.html +10 -0
  102. package/conformance/sheet/sheet-contract-whole.meta +5 -0
  103. package/conformance/sheet/sheet-hostile-01.expected.json +18 -0
  104. package/conformance/sheet/sheet-hostile-01.html +6 -0
  105. package/conformance/sheet/sheet-hostile-01.meta +5 -0
  106. package/conformance/sheet/sheet-hostile-02.expected.json +18 -0
  107. package/conformance/sheet/sheet-hostile-02.html +6 -0
  108. package/conformance/sheet/sheet-hostile-02.meta +5 -0
  109. package/conformance/sheet/sheet-hostile-03.expected.json +18 -0
  110. package/conformance/sheet/sheet-hostile-03.html +6 -0
  111. package/conformance/sheet/sheet-hostile-03.meta +5 -0
  112. package/conformance/sheet/sheet-hostile-04.expected.json +18 -0
  113. package/conformance/sheet/sheet-hostile-04.html +6 -0
  114. package/conformance/sheet/sheet-hostile-04.meta +5 -0
  115. package/conformance/sheet/sheet-hostile-05.expected.json +18 -0
  116. package/conformance/sheet/sheet-hostile-05.html +6 -0
  117. package/conformance/sheet/sheet-hostile-05.meta +5 -0
  118. package/conformance/sheet/sheet-hostile-06.expected.json +21 -0
  119. package/conformance/sheet/sheet-hostile-06.html +6 -0
  120. package/conformance/sheet/sheet-hostile-06.meta +5 -0
  121. package/conformance/sheet/sheet-hostile-07.expected.json +21 -0
  122. package/conformance/sheet/sheet-hostile-07.html +6 -0
  123. package/conformance/sheet/sheet-hostile-07.meta +5 -0
  124. package/conformance/sheet/sheet-hostile-08.expected.json +21 -0
  125. package/conformance/sheet/sheet-hostile-08.html +6 -0
  126. package/conformance/sheet/sheet-hostile-08.meta +5 -0
  127. package/conformance/sheet/sheet-hostile-09.expected.json +21 -0
  128. package/conformance/sheet/sheet-hostile-09.html +6 -0
  129. package/conformance/sheet/sheet-hostile-09.meta +5 -0
  130. package/conformance/sheet/sheet-hostile-10.expected.json +21 -0
  131. package/conformance/sheet/sheet-hostile-10.html +6 -0
  132. package/conformance/sheet/sheet-hostile-10.meta +5 -0
  133. package/conformance/sheet/sheet-hostile-11.expected.json +22 -0
  134. package/conformance/sheet/sheet-hostile-11.html +6 -0
  135. package/conformance/sheet/sheet-hostile-11.meta +5 -0
  136. package/conformance/sheet/sheet-hostile-12.expected.json +18 -0
  137. package/conformance/sheet/sheet-hostile-12.html +6 -0
  138. package/conformance/sheet/sheet-hostile-12.meta +5 -0
  139. package/conformance/sheet/sheet-hostile-13.expected.json +18 -0
  140. package/conformance/sheet/sheet-hostile-13.html +6 -0
  141. package/conformance/sheet/sheet-hostile-13.meta +5 -0
  142. package/conformance/sheet/sheet-hostile-14.expected.json +21 -0
  143. package/conformance/sheet/sheet-hostile-14.html +6 -0
  144. package/conformance/sheet/sheet-hostile-14.meta +5 -0
  145. package/conformance/sheet/sheet-hostile-15.expected.json +18 -0
  146. package/conformance/sheet/sheet-hostile-15.html +0 -0
  147. package/conformance/sheet/sheet-hostile-15.meta +5 -0
  148. package/conformance/sheet/sheet-hostile-16.expected.json +18 -0
  149. package/conformance/sheet/sheet-hostile-16.html +6 -0
  150. package/conformance/sheet/sheet-hostile-16.meta +5 -0
  151. package/conformance/sheet/sheet-hostile-17.expected.json +18 -0
  152. package/conformance/sheet/sheet-hostile-17.html +6 -0
  153. package/conformance/sheet/sheet-hostile-17.meta +5 -0
  154. package/conformance/sheet/sheet-hostile-18.expected.json +18 -0
  155. package/conformance/sheet/sheet-hostile-18.html +6 -0
  156. package/conformance/sheet/sheet-hostile-18.meta +5 -0
  157. package/conformance/sheet/sheet-hostile-19.expected.json +18 -0
  158. package/conformance/sheet/sheet-hostile-19.html +6 -0
  159. package/conformance/sheet/sheet-hostile-19.meta +5 -0
  160. package/conformance/sheet/sheet-hostile-20.expected.json +16 -0
  161. package/conformance/sheet/sheet-hostile-20.html +6 -0
  162. package/conformance/sheet/sheet-hostile-20.meta +5 -0
  163. package/conformance/sheet/sheet-hostile-21.expected.json +18 -0
  164. package/conformance/sheet/sheet-hostile-21.html +6 -0
  165. package/conformance/sheet/sheet-hostile-21.meta +5 -0
  166. package/conformance/sheet/sheet-hostile-22.expected.json +21 -0
  167. package/conformance/sheet/sheet-hostile-22.html +6 -0
  168. package/conformance/sheet/sheet-hostile-22.meta +5 -0
  169. package/conformance/sheet/sheet-hostile-23.expected.json +18 -0
  170. package/conformance/sheet/sheet-hostile-23.html +6 -0
  171. package/conformance/sheet/sheet-hostile-23.meta +5 -0
  172. package/conformance/sheet/sheet-hostile-24.expected.json +18 -0
  173. package/conformance/sheet/sheet-hostile-24.html +6 -0
  174. package/conformance/sheet/sheet-hostile-24.meta +5 -0
  175. package/conformance/sheet/sheet-hostile-25.expected.json +18 -0
  176. package/conformance/sheet/sheet-hostile-25.html +6 -0
  177. package/conformance/sheet/sheet-hostile-25.meta +5 -0
  178. package/conformance/sheet/sheet-hostile-26.expected.json +18 -0
  179. package/conformance/sheet/sheet-hostile-26.html +6 -0
  180. package/conformance/sheet/sheet-hostile-26.meta +5 -0
  181. package/conformance/sheet/sheet-hostile-27.expected.json +24 -0
  182. package/conformance/sheet/sheet-hostile-27.html +7 -0
  183. package/conformance/sheet/sheet-hostile-27.meta +5 -0
  184. package/conformance/sheet/sheet-hostile-28.expected.json +18 -0
  185. package/conformance/sheet/sheet-hostile-28.html +6 -0
  186. package/conformance/sheet/sheet-hostile-28.meta +5 -0
  187. package/conformance/sheet/sheet-hostile-29.expected.json +18 -0
  188. package/conformance/sheet/sheet-hostile-29.html +6 -0
  189. package/conformance/sheet/sheet-hostile-29.meta +5 -0
  190. package/conformance/sheet/sheet-hostile-30.expected.json +18 -0
  191. package/conformance/sheet/sheet-hostile-30.html +6 -0
  192. package/conformance/sheet/sheet-hostile-30.meta +5 -0
  193. package/conformance/sheet/sheet-hostile-31.expected.json +19 -0
  194. package/conformance/sheet/sheet-hostile-31.html +6 -0
  195. package/conformance/sheet/sheet-hostile-31.meta +5 -0
  196. package/conformance/sheet/sheet-hostile-32.expected.json +24 -0
  197. package/conformance/sheet/sheet-hostile-32.html +6 -0
  198. package/conformance/sheet/sheet-hostile-32.meta +5 -0
  199. package/conformance/sheet/sheet-hostile-33.expected.json +18 -0
  200. package/conformance/sheet/sheet-hostile-33.html +6 -0
  201. package/conformance/sheet/sheet-hostile-33.meta +5 -0
  202. package/conformance/sheet/sheet-hostile-34.expected.json +24 -0
  203. package/conformance/sheet/sheet-hostile-34.html +8 -0
  204. package/conformance/sheet/sheet-hostile-34.meta +5 -0
  205. package/conformance/sheet/sheet-hostile-35.expected.json +24 -0
  206. package/conformance/sheet/sheet-hostile-35.html +8 -0
  207. package/conformance/sheet/sheet-hostile-35.meta +5 -0
  208. package/conformance/sheet/sheet-patch-append.after.html +27 -0
  209. package/conformance/sheet/sheet-patch-append.after.json +143 -0
  210. package/conformance/sheet/sheet-patch-append.data.json +15 -0
  211. package/conformance/sheet/sheet-patch-append.html +25 -0
  212. package/conformance/sheet/sheet-patch-append.meta +5 -0
  213. package/conformance/sheet/sheet-patch-append.write.json +7 -0
  214. package/conformance/sheet/sheet-patch-bad-range.data.json +11 -0
  215. package/conformance/sheet/sheet-patch-bad-range.error.json +6 -0
  216. package/conformance/sheet/sheet-patch-bad-range.html +25 -0
  217. package/conformance/sheet/sheet-patch-bad-range.meta +5 -0
  218. package/conformance/sheet/sheet-patch-bad-value.data.json +13 -0
  219. package/conformance/sheet/sheet-patch-bad-value.error.json +6 -0
  220. package/conformance/sheet/sheet-patch-bad-value.html +25 -0
  221. package/conformance/sheet/sheet-patch-bad-value.meta +5 -0
  222. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.after.html +25 -0
  223. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.after.json +129 -0
  224. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.data.json +11 -0
  225. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.html +25 -0
  226. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.meta +5 -0
  227. package/conformance/sheet/sheet-patch-clean-tab-beside-edited.write.json +7 -0
  228. package/conformance/sheet/sheet-patch-covered-cell.data.json +10 -0
  229. package/conformance/sheet/sheet-patch-covered-cell.error.json +6 -0
  230. package/conformance/sheet/sheet-patch-covered-cell.html +10 -0
  231. package/conformance/sheet/sheet-patch-covered-cell.meta +5 -0
  232. package/conformance/sheet/sheet-patch-empty.data.json +1 -0
  233. package/conformance/sheet/sheet-patch-empty.error.json +6 -0
  234. package/conformance/sheet/sheet-patch-empty.html +25 -0
  235. package/conformance/sheet/sheet-patch-empty.meta +5 -0
  236. package/conformance/sheet/sheet-patch-fallback-route.after.html +24 -0
  237. package/conformance/sheet/sheet-patch-fallback-route.after.json +86 -0
  238. package/conformance/sheet/sheet-patch-fallback-route.data.json +10 -0
  239. package/conformance/sheet/sheet-patch-fallback-route.html +24 -0
  240. package/conformance/sheet/sheet-patch-fallback-route.meta +5 -0
  241. package/conformance/sheet/sheet-patch-fallback-route.write.json +7 -0
  242. package/conformance/sheet/sheet-patch-first-tab.after.html +25 -0
  243. package/conformance/sheet/sheet-patch-first-tab.after.json +129 -0
  244. package/conformance/sheet/sheet-patch-first-tab.data.json +10 -0
  245. package/conformance/sheet/sheet-patch-first-tab.html +25 -0
  246. package/conformance/sheet/sheet-patch-first-tab.meta +5 -0
  247. package/conformance/sheet/sheet-patch-first-tab.write.json +7 -0
  248. package/conformance/sheet/sheet-patch-formula.after.html +25 -0
  249. package/conformance/sheet/sheet-patch-formula.after.json +128 -0
  250. package/conformance/sheet/sheet-patch-formula.data.json +11 -0
  251. package/conformance/sheet/sheet-patch-formula.html +25 -0
  252. package/conformance/sheet/sheet-patch-formula.meta +5 -0
  253. package/conformance/sheet/sheet-patch-formula.write.json +7 -0
  254. package/conformance/sheet/sheet-patch-no-sheet-rule.data.json +10 -0
  255. package/conformance/sheet/sheet-patch-no-sheet-rule.error.json +6 -0
  256. package/conformance/sheet/sheet-patch-no-sheet-rule.html +18 -0
  257. package/conformance/sheet/sheet-patch-no-sheet-rule.meta +5 -0
  258. package/conformance/sheet/sheet-patch-no-values.data.json +6 -0
  259. package/conformance/sheet/sheet-patch-no-values.error.json +6 -0
  260. package/conformance/sheet/sheet-patch-no-values.html +25 -0
  261. package/conformance/sheet/sheet-patch-no-values.meta +5 -0
  262. package/conformance/sheet/sheet-patch-not-canonical.data.json +11 -0
  263. package/conformance/sheet/sheet-patch-not-canonical.error.json +6 -0
  264. package/conformance/sheet/sheet-patch-not-canonical.html +25 -0
  265. package/conformance/sheet/sheet-patch-not-canonical.meta +5 -0
  266. package/conformance/sheet/sheet-patch-outside-sheet.data.json +11 -0
  267. package/conformance/sheet/sheet-patch-outside-sheet.error.json +6 -0
  268. package/conformance/sheet/sheet-patch-outside-sheet.html +25 -0
  269. package/conformance/sheet/sheet-patch-outside-sheet.meta +5 -0
  270. package/conformance/sheet/sheet-patch-range.after.html +25 -0
  271. package/conformance/sheet/sheet-patch-range.after.json +126 -0
  272. package/conformance/sheet/sheet-patch-range.data.json +11 -0
  273. package/conformance/sheet/sheet-patch-range.html +25 -0
  274. package/conformance/sheet/sheet-patch-range.meta +5 -0
  275. package/conformance/sheet/sheet-patch-range.write.json +7 -0
  276. package/conformance/sheet/sheet-patch-two-tabs.after.html +25 -0
  277. package/conformance/sheet/sheet-patch-two-tabs.after.json +130 -0
  278. package/conformance/sheet/sheet-patch-two-tabs.data.json +20 -0
  279. package/conformance/sheet/sheet-patch-two-tabs.html +25 -0
  280. package/conformance/sheet/sheet-patch-two-tabs.meta +5 -0
  281. package/conformance/sheet/sheet-patch-two-tabs.write.json +7 -0
  282. package/conformance/sheet/sheet-patch-typed.after.html +25 -0
  283. package/conformance/sheet/sheet-patch-typed.after.json +121 -0
  284. package/conformance/sheet/sheet-patch-typed.data.json +13 -0
  285. package/conformance/sheet/sheet-patch-typed.html +25 -0
  286. package/conformance/sheet/sheet-patch-typed.meta +5 -0
  287. package/conformance/sheet/sheet-patch-typed.write.json +7 -0
  288. package/conformance/sheet/sheet-patch-unknown-sheet.data.json +11 -0
  289. package/conformance/sheet/sheet-patch-unknown-sheet.error.json +6 -0
  290. package/conformance/sheet/sheet-patch-unknown-sheet.html +25 -0
  291. package/conformance/sheet/sheet-patch-unknown-sheet.meta +5 -0
  292. package/conformance/sheet/sheet-read-bad-range.error.json +6 -0
  293. package/conformance/sheet/sheet-read-bad-range.html +25 -0
  294. package/conformance/sheet/sheet-read-bad-range.meta +6 -0
  295. package/conformance/sheet/sheet-read-bad-render.error.json +6 -0
  296. package/conformance/sheet/sheet-read-bad-render.html +25 -0
  297. package/conformance/sheet/sheet-read-bad-render.meta +6 -0
  298. package/conformance/sheet/sheet-read-digest.expected.json +91 -0
  299. package/conformance/sheet/sheet-read-digest.html +25 -0
  300. package/conformance/sheet/sheet-read-digest.meta +6 -0
  301. package/conformance/sheet/sheet-read-empty.expected.json +11 -0
  302. package/conformance/sheet/sheet-read-empty.html +6 -0
  303. package/conformance/sheet/sheet-read-empty.meta +5 -0
  304. package/conformance/sheet/sheet-read-escapes.expected.json +108 -0
  305. package/conformance/sheet/sheet-read-escapes.html +22 -0
  306. package/conformance/sheet/sheet-read-escapes.meta +5 -0
  307. package/conformance/sheet/sheet-read-formulas-formatted.expected.json +298 -0
  308. package/conformance/sheet/sheet-read-formulas-formatted.html +16 -0
  309. package/conformance/sheet/sheet-read-formulas-formatted.meta +5 -0
  310. package/conformance/sheet/sheet-read-grid-in-comment.expected.json +85 -0
  311. package/conformance/sheet/sheet-read-grid-in-comment.html +24 -0
  312. package/conformance/sheet/sheet-read-grid-in-comment.meta +5 -0
  313. package/conformance/sheet/sheet-read-grid-in-script.expected.json +85 -0
  314. package/conformance/sheet/sheet-read-grid-in-script.html +24 -0
  315. package/conformance/sheet/sheet-read-grid-in-script.meta +5 -0
  316. package/conformance/sheet/sheet-read-grid-in-template.expected.json +85 -0
  317. package/conformance/sheet/sheet-read-grid-in-template.html +24 -0
  318. package/conformance/sheet/sheet-read-grid-in-template.meta +5 -0
  319. package/conformance/sheet/sheet-read-grid-in-textarea.expected.json +85 -0
  320. package/conformance/sheet/sheet-read-grid-in-textarea.html +24 -0
  321. package/conformance/sheet/sheet-read-grid-in-textarea.meta +5 -0
  322. package/conformance/sheet/sheet-read-grid-outside-read-by-rule.expected.json +94 -0
  323. package/conformance/sheet/sheet-read-grid-outside-read-by-rule.html +24 -0
  324. package/conformance/sheet/sheet-read-grid-outside-read-by-rule.meta +5 -0
  325. package/conformance/sheet/sheet-read-grid-outside-sheet.expected.json +85 -0
  326. package/conformance/sheet/sheet-read-grid-outside-sheet.html +24 -0
  327. package/conformance/sheet/sheet-read-grid-outside-sheet.meta +5 -0
  328. package/conformance/sheet/sheet-read-hand-edited.expected.json +85 -0
  329. package/conformance/sheet/sheet-read-hand-edited.html +18 -0
  330. package/conformance/sheet/sheet-read-hand-edited.meta +5 -0
  331. package/conformance/sheet/sheet-read-index.expected.json +19 -0
  332. package/conformance/sheet/sheet-read-index.html +25 -0
  333. package/conformance/sheet/sheet-read-index.meta +6 -0
  334. package/conformance/sheet/sheet-read-merged.expected.json +59 -0
  335. package/conformance/sheet/sheet-read-merged.html +10 -0
  336. package/conformance/sheet/sheet-read-merged.meta +5 -0
  337. package/conformance/sheet/sheet-read-no-tag.error.json +5 -0
  338. package/conformance/sheet/sheet-read-no-tag.html +18 -0
  339. package/conformance/sheet/sheet-read-no-tag.meta +5 -0
  340. package/conformance/sheet/sheet-read-padded-name-index.expected.json +19 -0
  341. package/conformance/sheet/sheet-read-padded-name-index.html +25 -0
  342. package/conformance/sheet/sheet-read-padded-name-index.meta +6 -0
  343. package/conformance/sheet/sheet-read-padded-name-lookup.expected.json +83 -0
  344. package/conformance/sheet/sheet-read-padded-name-lookup.html +25 -0
  345. package/conformance/sheet/sheet-read-padded-name-lookup.meta +6 -0
  346. package/conformance/sheet/sheet-read-padded-name-trimmed-lookup.error.json +6 -0
  347. package/conformance/sheet/sheet-read-padded-name-trimmed-lookup.html +25 -0
  348. package/conformance/sheet/sheet-read-padded-name-trimmed-lookup.meta +6 -0
  349. package/conformance/sheet/sheet-read-padded-name.expected.json +128 -0
  350. package/conformance/sheet/sheet-read-padded-name.html +25 -0
  351. package/conformance/sheet/sheet-read-padded-name.meta +5 -0
  352. package/conformance/sheet/sheet-read-range-cells.expected.json +55 -0
  353. package/conformance/sheet/sheet-read-range-cells.html +25 -0
  354. package/conformance/sheet/sheet-read-range-cells.meta +6 -0
  355. package/conformance/sheet/sheet-read-range-first-tab.expected.json +12 -0
  356. package/conformance/sheet/sheet-read-range-first-tab.html +25 -0
  357. package/conformance/sheet/sheet-read-range-first-tab.meta +6 -0
  358. package/conformance/sheet/sheet-read-range-formulas.expected.json +24 -0
  359. package/conformance/sheet/sheet-read-range-formulas.html +25 -0
  360. package/conformance/sheet/sheet-read-range-formulas.meta +6 -0
  361. package/conformance/sheet/sheet-read-range-values.expected.json +24 -0
  362. package/conformance/sheet/sheet-read-range-values.html +25 -0
  363. package/conformance/sheet/sheet-read-range-values.meta +6 -0
  364. package/conformance/sheet/sheet-read-sheet-by-id.expected.json +46 -0
  365. package/conformance/sheet/sheet-read-sheet-by-id.html +25 -0
  366. package/conformance/sheet/sheet-read-sheet-by-id.meta +6 -0
  367. package/conformance/sheet/sheet-read-sheet-by-name.expected.json +46 -0
  368. package/conformance/sheet/sheet-read-sheet-by-name.html +25 -0
  369. package/conformance/sheet/sheet-read-sheet-by-name.meta +6 -0
  370. package/conformance/sheet/sheet-read-styled.expected.json +48 -0
  371. package/conformance/sheet/sheet-read-styled.html +10 -0
  372. package/conformance/sheet/sheet-read-styled.meta +5 -0
  373. package/conformance/sheet/sheet-read-tiny.expected.json +85 -0
  374. package/conformance/sheet/sheet-read-tiny.html +18 -0
  375. package/conformance/sheet/sheet-read-tiny.meta +5 -0
  376. package/conformance/sheet/sheet-read-two-tabs.expected.json +128 -0
  377. package/conformance/sheet/sheet-read-two-tabs.html +25 -0
  378. package/conformance/sheet/sheet-read-two-tabs.meta +5 -0
  379. package/conformance/sheet/sheet-read-unknown-sheet.error.json +6 -0
  380. package/conformance/sheet/sheet-read-unknown-sheet.html +25 -0
  381. package/conformance/sheet/sheet-read-unknown-sheet.meta +6 -0
  382. package/conformance/sheet/sheet-read-version-1-codec.error.json +5 -0
  383. package/conformance/sheet/sheet-read-version-1-codec.html +25 -0
  384. package/conformance/sheet/sheet-read-version-1-codec.meta +5 -0
  385. package/conformance/sheet/sheet-write-whole-refused.data.json +3 -0
  386. package/conformance/sheet/sheet-write-whole-refused.error.json +7 -0
  387. package/conformance/sheet/sheet-write-whole-refused.html +25 -0
  388. package/conformance/sheet/sheet-write-whole-refused.meta +5 -0
  389. package/conformance/sheet-guard-extraction.json +616 -0
  390. package/conformance/sheet-guard.json +32362 -0
  391. package/conformance/sheet-read-shapes.json +1 -0
  392. package/dist/hyper-html-api.engine.esm.js +1 -1
  393. package/dist/hyper-html-api.engine.min.js +1 -1
  394. package/dist/hyper-html-api.min.js +7 -7
  395. package/dist/hyper-html-api.upgrade.min.js +2 -2
  396. package/package.json +5 -3
  397. package/src/codecs/sheet.js +127 -0
  398. package/src/codecs/sheet.vendor.js +887 -0
  399. package/src/dom-api.js +20 -11
  400. package/src/engine/apply.js +12 -1
  401. package/src/engine/codecs.js +80 -0
  402. package/src/engine/errors.js +20 -1
  403. package/src/engine/extract.js +11 -0
  404. package/src/engine/index.js +9 -5
  405. package/src/engine/round-trip.js +16 -10
  406. package/src/engine/rule-syntax.js +16 -0
  407. package/src/engine/rules-tag.js +3 -2
  408. package/src/engine/write-plan.js +2 -0
  409. package/src/patch-document.js +122 -0
  410. package/src/read.js +163 -0
  411. package/src/upgrade/codec-carry.js +23 -0
  412. package/src/upgrade/extract-all.js +12 -3
  413. package/src/upgrade/migrate.js +30 -3
  414. package/src/upgrade/rules-tags.js +4 -4
  415. package/src/write.js +44 -5
package/src/read.js ADDED
@@ -0,0 +1,163 @@
1
+ import cheerioAdapter from './adapters/cheerio.js'
2
+ import { extract } from './engine/extract.js'
3
+ import { findRulesIn } from './engine/rules-tag.js'
4
+ import { codecOf } from './engine/rule-syntax.js'
5
+ import { sheet } from './codecs/sheet.js'
6
+ import { checkCodecRules } from './engine/codecs.js'
7
+ import { holeMarker, QueryRefused } from './codecs/sheet.vendor.js'
8
+
9
+ // The URL parameters a host passes to readDocument as `query`, from a
10
+ // URLSearchParams. Returns null when none of them is present (a whole read,
11
+ // exactly as before). Each may appear once with a value, and none may be
12
+ // mixed with ?data= (a caller-supplied rule set); anything else is
13
+ // QueryRefused('bad_query'), which hosts answer with 400. Values are not
14
+ // checked here: readDocument refuses a bad one with its own code.
15
+ export const SHEET_QUERY_PARAMS = ['shape', 'sheet', 'range', 'render']
16
+
17
+ export function parseSheetQuery(params) {
18
+ const query = {}
19
+ for (const key of SHEET_QUERY_PARAMS) {
20
+ const values = params.getAll(key)
21
+ if (values.length === 0) continue
22
+ if (values.length > 1) throw new QueryRefused('bad_query', `${key} appears ${values.length} times; send it once`)
23
+ if (values[0] === '') throw new QueryRefused('bad_query', `${key} is empty; give it a value or leave it out`)
24
+ query[key] = values[0]
25
+ }
26
+ if (Object.keys(query).length === 0) return null
27
+ if (params.has('data')) throw new QueryRefused('bad_query', `data cannot be combined with ${Object.keys(query).join(', ')}; a sheet query reads the document's own rules`)
28
+ return query
29
+ }
30
+
31
+ // Reads a document's data through its rules tag, from source. `load` is
32
+ // cheerio.load, passed in so this package keeps cheerio out of its browser
33
+ // bundles. Returns { data, version, fast }; data is null when no rules tag
34
+ // carries `token`.
35
+ //
36
+ // A codec with cut() lets a host skip parsing its regions: the source is cut,
37
+ // only the rest is parsed, and each hole hands the codec its region's bytes.
38
+ // The holes are verified first: every hole is a real element, each index
39
+ // appears exactly once and in order, and every hole lies under the element a
40
+ // rule naming that codec selected, which only that codec reads. Anything else
41
+ // (a grid inside a comment, a script or a textarea, a grid outside the sheet)
42
+ // discards the cut and reads the whole source through the tree. `fast` is false whenever a
43
+ // `<table grid` was read through the tree (that fallback, or a hand-edited table
44
+ // the cut left in place), so a host can count how often the slow route runs.
45
+ //
46
+ // options: { token = 'api', query, view, codecs = { sheet } }. query and view
47
+ // reach every codec rule (see src/codecs/sheet.js for the sheet's shapes).
48
+ export function readDocument(load, src, options = {}) {
49
+ const { token = 'api', query, view, codecs = { sheet } } = options
50
+ const call = { codecs, query, view }
51
+ const cutting = Object.entries(codecs).filter(([, codec]) => typeof codec.cut === 'function')
52
+ if (cutting.length > 1) throw new Error('hyper-html-api: readDocument supports one codec with cut()')
53
+
54
+ if (cutting.length === 1) {
55
+ const [name, codec] = cutting[0]
56
+ const marker = holeMarker(src)
57
+ const { rest, regions } = codec.cut(src, marker)
58
+ if (regions.length > 0) {
59
+ const $ = load(rest)
60
+ const root = $.root()
61
+ const found = findRulesIn(cheerioAdapter, root, token)
62
+ const fast = countGrids(rest) === regions.length
63
+ if (!found) return { data: null, version: null, fast }
64
+ if (codec === sheet && readHolesHold($, root, found.rules, name, marker, regions.length)) {
65
+ const region = (table) => regionBytes(table, src, marker, regions)
66
+ const opts = { ...call, version: found.version, region }
67
+ checkCodecRules(found.rules, opts)
68
+ let tree
69
+ const data = {}
70
+ for (const [key, rule] of Object.entries(found.rules)) {
71
+ const codec = codecOf(rule)
72
+ const context = codec ? root : (tree ??= load(src).root())
73
+ data[key] = extract(cheerioAdapter, context, { [key]: rule }, { ...opts, region: codec ? region : undefined })[key]
74
+ }
75
+ return { data, version: found.version, fast }
76
+ }
77
+ }
78
+ }
79
+
80
+ const fast = !src.includes(GRID)
81
+
82
+ const root = load(src).root()
83
+ const found = findRulesIn(cheerioAdapter, root, token)
84
+ if (!found) return { data: null, version: null, fast }
85
+ return { data: extract(cheerioAdapter, root, found.rules, { ...call, version: found.version }), version: found.version, fast }
86
+ }
87
+
88
+ // A document's rules tag as { rules, version }, or null when no tag carries
89
+ // `token`, without parsing its canonical tables: what a host needs to classify
90
+ // a request body (isPatchBody) before it reads or patches. Same cut and checks
91
+ // as readDocument; anything they reject is found by parsing the whole source.
92
+ export function documentRules(load, src, options = {}) {
93
+ const { token = 'api', codecs = { sheet } } = options
94
+ const cutting = Object.entries(codecs).filter(([, codec]) => typeof codec.cut === 'function')
95
+ if (cutting.length === 1) {
96
+ const [name, codec] = cutting[0]
97
+ const marker = holeMarker(src)
98
+ const { rest, regions } = codec.cut(src, marker)
99
+ if (regions.length > 0) {
100
+ const $ = load(rest)
101
+ const root = $.root()
102
+ const found = findRulesIn(cheerioAdapter, root, token)
103
+ if (!found) return null
104
+ if (holesHold($, root, found.rules, name, marker, regions.length)) return { rules: found.rules, version: found.version }
105
+ }
106
+ }
107
+ const found = findRulesIn(cheerioAdapter, load(src).root(), token)
108
+ return found ? { rules: found.rules, version: found.version } : null
109
+ }
110
+
111
+ const GRID = '<table grid'
112
+
113
+ function countGrids(text) {
114
+ let n = 0
115
+ for (let at = text.indexOf(GRID); at !== -1; at = text.indexOf(GRID, at + 1)) n++
116
+ return n
117
+ }
118
+
119
+ export function holesHold($, root, rules, name, marker, count) {
120
+ const holes = $('table[data-hole]').toArray()
121
+ if (holes.length !== count) return false
122
+ const containers = codecContainers(root, rules, name)
123
+ if (containers.length === 0) return false
124
+ return holes.every((hole, n) =>
125
+ hole.attribs['data-hole'] === `${marker}-${n}` &&
126
+ containers.some((container) => container === hole || $.contains(container, hole)))
127
+ }
128
+
129
+ // Only this selector cannot match within canonical grid bytes or depend on their children.
130
+ const SHEET_CONTAINER = /^\[items\s*=\s*(?:sheets|"sheets"|'sheets')\]$/
131
+
132
+ function readHolesHold($, root, rules, name, marker, count) {
133
+ if (!rules || typeof rules !== 'object' || Array.isArray(rules)) return false
134
+ for (const rule of Object.values(rules)) {
135
+ const form = codecOf(rule)
136
+ if (form && (form.name !== name || !SHEET_CONTAINER.test(form.selector))) return false
137
+ }
138
+ if (!holesHold($, root, rules, name, marker, count)) return false
139
+ const containers = new Set(codecContainers(root, rules, name))
140
+ return $('table[grid]').length === count && $('table[data-hole]').toArray().every(hole =>
141
+ hole.parent?.name === 'section' && Object.hasOwn(hole.parent.attribs, 'item') && containers.has(hole.parent.parent))
142
+ }
143
+
144
+ // The element each top level `selector@codec(name)` rule selects, as the engine
145
+ // would read it: the first match, or the document itself for a bare "@codec(name)".
146
+ function codecContainers(root, rules, name) {
147
+ if (!rules || typeof rules !== 'object' || Array.isArray(rules)) return []
148
+ const out = []
149
+ for (const rule of Object.values(rules)) {
150
+ const form = codecOf(rule)
151
+ if (!form || form.name !== name) continue
152
+ const node = form.selector ? cheerioAdapter.find(root, form.selector)[0] : root
153
+ if (node && node.get(0)) out.push(node.get(0))
154
+ }
155
+ return out
156
+ }
157
+
158
+ function regionBytes(table, src, marker, regions) {
159
+ const hole = cheerioAdapter.attr(table, 'data-hole')
160
+ if (!hole || !hole.startsWith(`${marker}-`)) return null
161
+ const region = regions[Number(hole.slice(marker.length + 1))]
162
+ return region ? src.slice(region.start, region.end) : null
163
+ }
@@ -0,0 +1,23 @@
1
+ import { codecOf } from '../engine/rule-syntax.js'
2
+
3
+ // Under a version 2 rules tag, a top level `@codec(name)` key (a sheet) is
4
+ // carried whole on upgrade: the extract side keeps its container's markup as
5
+ // a string, and the migrate side puts that string into the same key's
6
+ // container in the new template. The value is never read into data and
7
+ // written back, so the codec needs no whole-value write and the canonical
8
+ // bytes its grammar reads stay as they were.
9
+ export function splitCarried(rules, version) {
10
+ if (version !== '2' || !rules || typeof rules !== 'object' || Array.isArray(rules)) return { rules, carried: [] }
11
+ const rest = {}
12
+ const carried = []
13
+ for (const [key, rule] of Object.entries(rules)) {
14
+ const codec = codecOf(rule)
15
+ if (codec) carried.push({ key, selector: codec.selector })
16
+ else rest[key] = rule
17
+ }
18
+ return { rules: rest, carried }
19
+ }
20
+
21
+ export function carriedNode(adapter, root, selector) {
22
+ return adapter.find(root, selector)[0] || null
23
+ }
@@ -1,17 +1,26 @@
1
1
  import { extract } from '../engine/extract.js'
2
2
  import domAdapter from '../adapters/dom.js'
3
3
  import { collectRulesTags } from './rules-tags.js'
4
+ import { splitCarried, carriedNode } from './codec-carry.js'
4
5
  import { readMeta } from './check.js'
5
6
 
6
7
  /**
7
8
  * Keyed extraction: one entry per rules tag under `root`, keyed by the
8
9
  * normalized data-rules-name value. This is the v1 side of an upgrade:
9
- * pack the page's data into labeled boxes.
10
+ * pack the page's data into labeled boxes. A `@codec(name)` key (a sheet,
11
+ * version 2 tags only) packs as its container's markup string, which a
12
+ * transform passes through unchanged.
10
13
  */
11
14
  export function extractAllFrom(adapter, root) {
12
15
  const dataByName = {}
13
- for (const { name, rules } of collectRulesTags(adapter, root)) {
14
- dataByName[name] = extract(adapter, root, rules)
16
+ for (const { name, rules, version } of collectRulesTags(adapter, root)) {
17
+ const { rules: plain, carried } = splitCarried(rules, version)
18
+ const data = extract(adapter, root, plain, { version })
19
+ for (const { key, selector } of carried) {
20
+ const node = carriedNode(adapter, root, selector)
21
+ data[key] = node ? adapter.prop(node, 'innerHTML') : null
22
+ }
23
+ dataByName[name] = data
15
24
  }
16
25
  return dataByName
17
26
  }
@@ -1,12 +1,15 @@
1
1
  import { apply } from '../engine/apply.js'
2
2
  import { collectRulesTags } from './rules-tags.js'
3
3
  import { shapeMatch, countScalarLeaves } from './shape-match.js'
4
+ import { splitCarried, carriedNode } from './codec-carry.js'
4
5
 
5
6
  /**
6
7
  * Pour keyed v1 data into the v2 document. Exact-name join: each v2 rules
7
8
  * tag pulls the entry with the same normalized name; tags with no matching
8
9
  * entry keep their template defaults; v1 entries with no matching tag are
9
10
  * counted as discarded. Renames are the transform's job, not the join's.
11
+ * A `@codec(name)` key takes its markup string whole into the new
12
+ * template's container for that key.
10
13
  */
11
14
  export function migrateInto(adapter, root, dataByName) {
12
15
  const tags = collectRulesTags(adapter, root)
@@ -14,15 +17,28 @@ export function migrateInto(adapter, root, dataByName) {
14
17
  const byName = {}
15
18
  const v2Names = new Set()
16
19
 
17
- for (const { name, rules } of tags) {
20
+ for (const { name, rules, version } of tags) {
18
21
  v2Names.add(name)
19
22
  if (!Object.prototype.hasOwnProperty.call(dataByName, name)) {
20
23
  byName[name] = { carriedOver: 0, discarded: 0, listItems: 0 }
21
24
  continue
22
25
  }
23
- const { data, summary } = shapeMatch(dataByName[name], rules)
26
+ const { rules: plain, carried } = splitCarried(rules, version)
27
+ const entry = dataByName[name]
28
+ const { data, summary } = shapeMatch(withoutCarried(entry, carried), plain)
24
29
  // A row whose nested list is empty at that spot in v2 grows from the page's seeds, as a write does.
25
- apply(adapter, root, rules, data, { templateAttr: 'cms-template' })
30
+ apply(adapter, root, plain, data, { templateAttr: 'cms-template' })
31
+ for (const { key, selector } of carried) {
32
+ const markup = isPlainObject(entry) ? entry[key] : undefined
33
+ if (markup === undefined || markup === null) continue
34
+ const node = carriedNode(adapter, root, selector)
35
+ if (typeof markup === 'string' && node) {
36
+ adapter.prop(node, 'innerHTML', markup)
37
+ summary.carriedOver += 1
38
+ } else {
39
+ summary.discarded += 1
40
+ }
41
+ }
26
42
  byName[name] = summary
27
43
  totals.carriedOver += summary.carriedOver
28
44
  totals.discarded += summary.discarded
@@ -35,3 +51,14 @@ export function migrateInto(adapter, root, dataByName) {
35
51
 
36
52
  return { totals, byName, rulesTagCount: tags.length }
37
53
  }
54
+
55
+ function isPlainObject(value) {
56
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
57
+ }
58
+
59
+ function withoutCarried(entry, carried) {
60
+ if (!carried.length || !isPlainObject(entry)) return entry
61
+ const rest = { ...entry }
62
+ for (const { key } of carried) delete rest[key]
63
+ return rest
64
+ }
@@ -1,5 +1,5 @@
1
1
  import { parseRelaxed } from '../engine/rules.js'
2
- import { SUPPORTED_VERSION } from '../engine/rules-tag.js'
2
+ import { SUPPORTED_VERSIONS } from '../engine/rules-tag.js'
3
3
  import { UnknownRulesVersion } from '../engine/errors.js'
4
4
 
5
5
  const RULES_SELECTOR = 'script[data-rules-name]'
@@ -9,7 +9,7 @@ export function normalizeName(raw) {
9
9
  }
10
10
 
11
11
  /**
12
- * Collect every rules tag under `root` as [{ name, rules }], keyed by the
12
+ * Collect every rules tag under `root` as [{ name, rules, version }], keyed by the
13
13
  * whitespace-normalized data-rules-name attribute value. Duplicate names:
14
14
  * first tag wins with a warning (mirrors findRulesIn). Unsupported
15
15
  * data-rules-version throws so engine-format skew fails loudly instead of
@@ -29,9 +29,9 @@ export function collectRulesTags(adapter, root) {
29
29
  continue
30
30
  }
31
31
  const version = adapter.attr(tag, 'data-rules-version')
32
- if (version !== SUPPORTED_VERSION) throw new UnknownRulesVersion(version)
32
+ if (!SUPPORTED_VERSIONS.includes(version)) throw new UnknownRulesVersion(version)
33
33
  seen.add(name)
34
- out.push({ name, rules: parseRelaxed(adapter.text(tag)) })
34
+ out.push({ name, rules: parseRelaxed(adapter.text(tag)), version })
35
35
  }
36
36
  return out
37
37
  }
package/src/write.js CHANGED
@@ -7,18 +7,23 @@ import { planWrite } from './engine/write-plan.js'
7
7
  import { assertRoundTrip } from './engine/round-trip.js'
8
8
  import { trackAdapter, spliceDocument } from './engine/splice.js'
9
9
  import { NoRulesTag, RulesParseError, WriteRejected } from './engine/errors.js'
10
+ import { sheet } from './codecs/sheet.js'
11
+ import { codecOf } from './engine/rule-syntax.js'
10
12
 
11
13
  // Applies a JSON body to a document's source through caller-provided rules
12
14
  // (options.rules) or the document's own rules tag, under the content-only
13
15
  // policy. `load` is cheerio.load, passed in so this package keeps cheerio out
14
16
  // of its browser bundles. Returns the new source and whether anything changed.
15
17
  // Throws NoRulesTag, RulesParseError, WriteRejected, WriteRefused, ShapeMismatch,
16
- // EmptyListInsert or WriteNotRoundTripped, always before producing any output.
18
+ // EmptyListInsert, UnknownCodec or WriteNotRoundTripped, always before producing
19
+ // any output. options.codecs maps a name to a codec for `@codec(name)` rules,
20
+ // which only a version 2 rules tag may use (default: the sheet codec).
17
21
  export function writeDocument(load, src, data, options = {}) {
18
- const { token = 'api' } = options
22
+ const { token = 'api', codecs = { sheet } } = options
19
23
  const $ = load(src, { sourceCodeLocationInfo: true })
20
24
  const root = $.root()
21
25
  let rules
26
+ let version = '1'
22
27
  if (Object.prototype.hasOwnProperty.call(options, 'rules')) {
23
28
  rules = options.rules
24
29
  if (rules === null || !['string', 'object'].includes(typeof rules)) {
@@ -28,10 +33,11 @@ export function writeDocument(load, src, data, options = {}) {
28
33
  const found = findRulesIn(cheerioAdapter, root, token)
29
34
  if (!found) throw new NoRulesTag(token)
30
35
  rules = found.rules
36
+ version = found.version
31
37
  }
32
- const plan = planWrite(cheerioAdapter, root, rules, data)
38
+ const plan = planWrite(cheerioAdapter, root, rules, data, { version, codecs })
33
39
  if (plan.unknownKeys.length || plan.unmatched.length) throw new WriteRejected(plan.unknownKeys, plan.unmatched)
34
- const opts = { templateAttr: 'cms-template' }
40
+ const opts = { templateAttr: 'cms-template', version, codecs }
35
41
  const tracker = trackAdapter(cheerioAdapter)
36
42
  const guarded = guardAdapter(tracker)
37
43
  apply(guarded, root, rules, data, opts)
@@ -46,7 +52,7 @@ export function writeDocument(load, src, data, options = {}) {
46
52
  // pays for the field it wrote, not for every rule in the document.
47
53
  const written = writtenRules(rules, data)
48
54
  const writtenRoot = load(html).root()
49
- assertRoundTrip(written, data, extract(cheerioAdapter, writtenRoot, written, { ...opts, allMatches: true }))
55
+ assertRoundTrip(written, data, extract(cheerioAdapter, writtenRoot, written, { ...opts, allMatches: true }), codecs)
50
56
  return { html, changed: html !== src, spliced }
51
57
  }
52
58
 
@@ -65,3 +71,36 @@ function writtenRules(rules, data) {
65
71
  function isPlainObject(value) {
66
72
  return value !== null && typeof value === 'object' && !Array.isArray(value)
67
73
  }
74
+
75
+ export { patchDocument, NOT_CANONICAL_MESSAGE } from './patch-document.js'
76
+
77
+ // The one rule every host uses to tell a patch body from a data write: an
78
+ // object whose `patch` value is an array and whose only other key is
79
+ // `operationId`, sent to a document whose rules have no top level key named
80
+ // `patch`. Anything else is a data write, exactly as before patches existed.
81
+ export function isPatchBody(body, rules) {
82
+ if (!isPlainObject(body) || !Array.isArray(body.patch)) return false
83
+ if (Object.keys(body).some((key) => key !== 'patch' && key !== 'operationId')) return false
84
+ return !(isPlainObject(rules) && Object.prototype.hasOwnProperty.call(rules, 'patch'))
85
+ }
86
+
87
+ // Rows in a write body, for a host's size limit: every array item counts, at
88
+ // any depth. Under a version 2 rules tag a top level key whose rule is a
89
+ // `@codec(name)` rule is skipped, because its value is not a list of rows (a
90
+ // sheet's size is its cells, and hosts limit those per patch).
91
+ export function countRows(value, rules, version = '1') {
92
+ if (version === '2' && isPlainObject(value) && isPlainObject(rules)) {
93
+ let sum = 0
94
+ for (const [key, item] of Object.entries(value)) {
95
+ if (!codecOf(rules[key])) sum += countAll(item)
96
+ }
97
+ return sum
98
+ }
99
+ return countAll(value)
100
+ }
101
+
102
+ function countAll(value) {
103
+ if (Array.isArray(value)) return value.reduce((sum, item) => sum + 1 + countAll(item), 0)
104
+ if (value !== null && typeof value === 'object') return Object.values(value).reduce((sum, item) => sum + countAll(item), 0)
105
+ return 0
106
+ }