@elaraai/east-ui 1.0.80 → 1.0.82

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 (353) hide show
  1. package/dist/src/charts/chart/index.d.ts +4 -0
  2. package/dist/src/charts/chart/index.d.ts.map +1 -1
  3. package/dist/src/charts/spec/index.d.ts +8 -0
  4. package/dist/src/charts/spec/index.d.ts.map +1 -1
  5. package/dist/src/collections/deck/index.d.ts +8 -0
  6. package/dist/src/collections/deck/index.d.ts.map +1 -1
  7. package/dist/src/collections/index.d.ts +2 -2
  8. package/dist/src/collections/index.d.ts.map +1 -1
  9. package/dist/src/collections/index.js.map +1 -1
  10. package/dist/src/collections/library/index.d.ts +4 -0
  11. package/dist/src/collections/library/index.d.ts.map +1 -1
  12. package/dist/src/collections/library/types.d.ts +4 -0
  13. package/dist/src/collections/library/types.d.ts.map +1 -1
  14. package/dist/src/collections/matrix/index.d.ts +56 -19
  15. package/dist/src/collections/matrix/index.d.ts.map +1 -1
  16. package/dist/src/collections/matrix/index.js +38 -24
  17. package/dist/src/collections/matrix/index.js.map +1 -1
  18. package/dist/src/collections/plan/assemble.d.ts +655 -2113
  19. package/dist/src/collections/plan/assemble.d.ts.map +1 -1
  20. package/dist/src/collections/plan/assemble.js +159 -136
  21. package/dist/src/collections/plan/assemble.js.map +1 -1
  22. package/dist/src/collections/plan/builders.d.ts +193 -40
  23. package/dist/src/collections/plan/builders.d.ts.map +1 -1
  24. package/dist/src/collections/plan/builders.js +162 -32
  25. package/dist/src/collections/plan/builders.js.map +1 -1
  26. package/dist/src/collections/plan/factories.d.ts +290 -116
  27. package/dist/src/collections/plan/factories.d.ts.map +1 -1
  28. package/dist/src/collections/plan/factories.js +281 -156
  29. package/dist/src/collections/plan/factories.js.map +1 -1
  30. package/dist/src/collections/plan/index.d.ts +111 -58
  31. package/dist/src/collections/plan/index.d.ts.map +1 -1
  32. package/dist/src/collections/plan/index.js +62 -31
  33. package/dist/src/collections/plan/index.js.map +1 -1
  34. package/dist/src/collections/plan/ir.d.ts +759 -1769
  35. package/dist/src/collections/plan/ir.d.ts.map +1 -1
  36. package/dist/src/collections/plan/ir.js +51 -27
  37. package/dist/src/collections/plan/ir.js.map +1 -1
  38. package/dist/src/collections/plan/pick.d.ts +89 -71
  39. package/dist/src/collections/plan/pick.d.ts.map +1 -1
  40. package/dist/src/collections/plan/pick.js +101 -86
  41. package/dist/src/collections/plan/pick.js.map +1 -1
  42. package/dist/src/collections/plan/root.d.ts +164 -77
  43. package/dist/src/collections/plan/root.d.ts.map +1 -1
  44. package/dist/src/collections/plan/root.js +350 -42
  45. package/dist/src/collections/plan/root.js.map +1 -1
  46. package/dist/src/collections/plan/series.d.ts +12524 -10527
  47. package/dist/src/collections/plan/series.d.ts.map +1 -1
  48. package/dist/src/collections/plan/series.js +1596 -298
  49. package/dist/src/collections/plan/series.js.map +1 -1
  50. package/dist/src/collections/plan/types.d.ts +6115 -2802
  51. package/dist/src/collections/plan/types.d.ts.map +1 -1
  52. package/dist/src/collections/plan/types.js +575 -170
  53. package/dist/src/collections/plan/types.js.map +1 -1
  54. package/dist/src/collections/sheet/bridge.d.ts +154 -43
  55. package/dist/src/collections/sheet/bridge.d.ts.map +1 -1
  56. package/dist/src/collections/sheet/bridge.js +383 -240
  57. package/dist/src/collections/sheet/bridge.js.map +1 -1
  58. package/dist/src/collections/sheet/columns.d.ts +66 -16
  59. package/dist/src/collections/sheet/columns.d.ts.map +1 -1
  60. package/dist/src/collections/sheet/columns.js +25 -25
  61. package/dist/src/collections/sheet/columns.js.map +1 -1
  62. package/dist/src/collections/sheet/context-bridge.d.ts +48 -0
  63. package/dist/src/collections/sheet/context-bridge.d.ts.map +1 -0
  64. package/dist/src/collections/sheet/context-bridge.js +216 -0
  65. package/dist/src/collections/sheet/context-bridge.js.map +1 -0
  66. package/dist/src/collections/sheet/draft-bridge.d.ts +37 -0
  67. package/dist/src/collections/sheet/draft-bridge.d.ts.map +1 -0
  68. package/dist/src/collections/sheet/draft-bridge.js +153 -0
  69. package/dist/src/collections/sheet/draft-bridge.js.map +1 -0
  70. package/dist/src/collections/sheet/drafts.d.ts +14 -0
  71. package/dist/src/collections/sheet/drafts.d.ts.map +1 -0
  72. package/dist/src/collections/sheet/drafts.js +14 -0
  73. package/dist/src/collections/sheet/drafts.js.map +1 -0
  74. package/dist/src/collections/sheet/editing-bridge.d.ts +36 -0
  75. package/dist/src/collections/sheet/editing-bridge.d.ts.map +1 -0
  76. package/dist/src/collections/sheet/editing-bridge.js +196 -0
  77. package/dist/src/collections/sheet/editing-bridge.js.map +1 -0
  78. package/dist/src/collections/sheet/editing-types.d.ts +268 -0
  79. package/dist/src/collections/sheet/editing-types.d.ts.map +1 -0
  80. package/dist/src/collections/sheet/editing-types.js +34 -0
  81. package/dist/src/collections/sheet/editing-types.js.map +1 -0
  82. package/dist/src/collections/sheet/edits.d.ts +37 -0
  83. package/dist/src/collections/sheet/edits.d.ts.map +1 -0
  84. package/dist/src/collections/sheet/edits.js +31 -0
  85. package/dist/src/collections/sheet/edits.js.map +1 -0
  86. package/dist/src/collections/sheet/group.d.ts +165 -0
  87. package/dist/src/collections/sheet/group.d.ts.map +1 -0
  88. package/dist/src/collections/sheet/group.js +52 -0
  89. package/dist/src/collections/sheet/group.js.map +1 -0
  90. package/dist/src/collections/sheet/index.d.ts +171 -27
  91. package/dist/src/collections/sheet/index.d.ts.map +1 -1
  92. package/dist/src/collections/sheet/index.js +99 -17
  93. package/dist/src/collections/sheet/index.js.map +1 -1
  94. package/dist/src/collections/sheet/link.d.ts +22 -16
  95. package/dist/src/collections/sheet/link.d.ts.map +1 -1
  96. package/dist/src/collections/sheet/link.js +18 -12
  97. package/dist/src/collections/sheet/link.js.map +1 -1
  98. package/dist/src/collections/sheet/registers.d.ts +152 -5
  99. package/dist/src/collections/sheet/registers.d.ts.map +1 -1
  100. package/dist/src/collections/sheet/registers.js +154 -5
  101. package/dist/src/collections/sheet/registers.js.map +1 -1
  102. package/dist/src/collections/sheet/root.d.ts +175 -26
  103. package/dist/src/collections/sheet/root.d.ts.map +1 -1
  104. package/dist/src/collections/sheet/root.js +150 -26
  105. package/dist/src/collections/sheet/root.js.map +1 -1
  106. package/dist/src/collections/sheet/seed-bridge.d.ts +14 -0
  107. package/dist/src/collections/sheet/seed-bridge.d.ts.map +1 -0
  108. package/dist/src/collections/sheet/seed-bridge.js +51 -0
  109. package/dist/src/collections/sheet/seed-bridge.js.map +1 -0
  110. package/dist/src/collections/sheet/sub-rows.d.ts +249 -0
  111. package/dist/src/collections/sheet/sub-rows.d.ts.map +1 -0
  112. package/dist/src/collections/sheet/sub-rows.js +222 -0
  113. package/dist/src/collections/sheet/sub-rows.js.map +1 -0
  114. package/dist/src/collections/sheet/transactions.d.ts +76 -0
  115. package/dist/src/collections/sheet/transactions.d.ts.map +1 -0
  116. package/dist/src/collections/sheet/transactions.js +40 -0
  117. package/dist/src/collections/sheet/transactions.js.map +1 -0
  118. package/dist/src/collections/sheet/types.d.ts +12034 -3651
  119. package/dist/src/collections/sheet/types.d.ts.map +1 -1
  120. package/dist/src/collections/sheet/types.js +365 -48
  121. package/dist/src/collections/sheet/types.js.map +1 -1
  122. package/dist/src/collections/table/index.d.ts +98 -123
  123. package/dist/src/collections/table/index.d.ts.map +1 -1
  124. package/dist/src/collections/table/index.js +97 -111
  125. package/dist/src/collections/table/index.js.map +1 -1
  126. package/dist/src/collections/table/types.d.ts +87 -31
  127. package/dist/src/collections/table/types.d.ts.map +1 -1
  128. package/dist/src/collections/table/types.js +51 -26
  129. package/dist/src/collections/table/types.js.map +1 -1
  130. package/dist/src/collections/value-tree/flatten.d.ts.map +1 -1
  131. package/dist/src/collections/value-tree/flatten.js +4 -1
  132. package/dist/src/collections/value-tree/flatten.js.map +1 -1
  133. package/dist/src/component.d.ts +5075 -4079
  134. package/dist/src/component.d.ts.map +1 -1
  135. package/dist/src/component.js +46 -46
  136. package/dist/src/component.js.map +1 -1
  137. package/dist/src/contracts/approval.d.ts +10 -8
  138. package/dist/src/contracts/approval.d.ts.map +1 -1
  139. package/dist/src/contracts/approval.js +10 -8
  140. package/dist/src/contracts/approval.js.map +1 -1
  141. package/dist/src/contracts/drag.d.ts +11 -9
  142. package/dist/src/contracts/drag.d.ts.map +1 -1
  143. package/dist/src/contracts/drag.js +28 -21
  144. package/dist/src/contracts/drag.js.map +1 -1
  145. package/dist/src/contracts/editing.d.ts +1283 -0
  146. package/dist/src/contracts/editing.d.ts.map +1 -0
  147. package/dist/src/contracts/editing.js +711 -0
  148. package/dist/src/contracts/editing.js.map +1 -0
  149. package/dist/src/contracts/index.d.ts +2 -1
  150. package/dist/src/contracts/index.d.ts.map +1 -1
  151. package/dist/src/contracts/index.js +2 -1
  152. package/dist/src/contracts/index.js.map +1 -1
  153. package/dist/src/contracts/pick.d.ts +6 -7
  154. package/dist/src/contracts/pick.d.ts.map +1 -1
  155. package/dist/src/contracts/pick.js +6 -7
  156. package/dist/src/contracts/pick.js.map +1 -1
  157. package/dist/src/contracts/review.d.ts +3 -1
  158. package/dist/src/contracts/review.d.ts.map +1 -1
  159. package/dist/src/contracts/review.js +3 -1
  160. package/dist/src/contracts/review.js.map +1 -1
  161. package/dist/src/contracts/source.d.ts +270 -80
  162. package/dist/src/contracts/source.d.ts.map +1 -1
  163. package/dist/src/contracts/source.js +357 -58
  164. package/dist/src/contracts/source.js.map +1 -1
  165. package/dist/src/display/chip-rail/index.d.ts.map +1 -1
  166. package/dist/src/display/chip-rail/index.js +10 -10
  167. package/dist/src/display/chip-rail/index.js.map +1 -1
  168. package/dist/src/extension.d.ts +2 -1
  169. package/dist/src/extension.d.ts.map +1 -1
  170. package/dist/src/extension.js +2 -1
  171. package/dist/src/extension.js.map +1 -1
  172. package/dist/src/format/index.d.ts +1 -154
  173. package/dist/src/format/index.d.ts.map +1 -1
  174. package/dist/src/format/types.d.ts +35 -23
  175. package/dist/src/format/types.d.ts.map +1 -1
  176. package/dist/src/format/types.js +16 -14
  177. package/dist/src/format/types.js.map +1 -1
  178. package/dist/src/forms/field/index.js +4 -4
  179. package/dist/src/forms/field/index.js.map +1 -1
  180. package/dist/src/forms/file-upload/index.js +16 -16
  181. package/dist/src/forms/file-upload/index.js.map +1 -1
  182. package/dist/src/index.d.ts +4 -2
  183. package/dist/src/index.d.ts.map +1 -1
  184. package/dist/src/index.js +12 -5
  185. package/dist/src/index.js.map +1 -1
  186. package/dist/src/internal.d.ts +8 -3
  187. package/dist/src/internal.d.ts.map +1 -1
  188. package/dist/src/internal.js +11 -3
  189. package/dist/src/internal.js.map +1 -1
  190. package/dist/src/layout/box/index.d.ts.map +1 -1
  191. package/dist/src/layout/box/index.js +47 -47
  192. package/dist/src/layout/box/index.js.map +1 -1
  193. package/dist/src/layout/flex/index.d.ts.map +1 -1
  194. package/dist/src/layout/flex/index.js +47 -47
  195. package/dist/src/layout/flex/index.js.map +1 -1
  196. package/dist/src/layout/grid/index.js +11 -11
  197. package/dist/src/layout/grid/index.js.map +1 -1
  198. package/dist/src/layout/scroll-area/index.d.ts.map +1 -1
  199. package/dist/src/layout/scroll-area/index.js +8 -8
  200. package/dist/src/layout/scroll-area/index.js.map +1 -1
  201. package/dist/src/layout/separator/index.d.ts.map +1 -1
  202. package/dist/src/layout/separator/index.js +6 -6
  203. package/dist/src/layout/separator/index.js.map +1 -1
  204. package/dist/src/layout/stack/index.d.ts.map +1 -1
  205. package/dist/src/layout/stack/index.js +46 -46
  206. package/dist/src/layout/stack/index.js.map +1 -1
  207. package/dist/src/layout/sticky/index.d.ts.map +1 -1
  208. package/dist/src/layout/sticky/index.js +8 -8
  209. package/dist/src/layout/sticky/index.js.map +1 -1
  210. package/dist/src/overlays/action-bar/index.d.ts.map +1 -1
  211. package/dist/src/overlays/action-bar/index.js +8 -8
  212. package/dist/src/overlays/action-bar/index.js.map +1 -1
  213. package/dist/src/platform/index.d.ts +1 -1
  214. package/dist/src/platform/index.d.ts.map +1 -1
  215. package/dist/src/platform/index.js +1 -1
  216. package/dist/src/platform/index.js.map +1 -1
  217. package/dist/src/platform/slice/impl.d.ts +62 -51
  218. package/dist/src/platform/slice/impl.d.ts.map +1 -1
  219. package/dist/src/platform/slice/impl.js +155 -72
  220. package/dist/src/platform/slice/impl.js.map +1 -1
  221. package/dist/src/platform/slice/index.d.ts +3744 -2226
  222. package/dist/src/platform/slice/index.d.ts.map +1 -1
  223. package/dist/src/platform/slice/index.js +43 -1
  224. package/dist/src/platform/slice/index.js.map +1 -1
  225. package/dist/src/runtime/collections/deck.d.ts +4 -0
  226. package/dist/src/runtime/collections/deck.d.ts.map +1 -1
  227. package/dist/src/runtime/collections/matrix.d.ts +10 -9
  228. package/dist/src/runtime/collections/matrix.d.ts.map +1 -1
  229. package/dist/src/runtime/collections/matrix.js +11 -6
  230. package/dist/src/runtime/collections/matrix.js.map +1 -1
  231. package/dist/src/runtime/collections/plan.d.ts +148 -65
  232. package/dist/src/runtime/collections/plan.d.ts.map +1 -1
  233. package/dist/src/runtime/collections/plan.js +155 -67
  234. package/dist/src/runtime/collections/plan.js.map +1 -1
  235. package/dist/src/runtime/collections/sheet.d.ts +56 -23
  236. package/dist/src/runtime/collections/sheet.d.ts.map +1 -1
  237. package/dist/src/runtime/collections/sheet.js +30 -22
  238. package/dist/src/runtime/collections/sheet.js.map +1 -1
  239. package/dist/src/runtime/collections/table.d.ts +18 -11
  240. package/dist/src/runtime/collections/table.d.ts.map +1 -1
  241. package/dist/src/runtime/collections/table.js +7 -4
  242. package/dist/src/runtime/collections/table.js.map +1 -1
  243. package/dist/src/shared/reify.d.ts +4 -46
  244. package/dist/src/shared/reify.d.ts.map +1 -1
  245. package/dist/src/shared/reify.js +4 -61
  246. package/dist/src/shared/reify.js.map +1 -1
  247. package/dist/src/shared/tree.d.ts +88 -0
  248. package/dist/src/shared/tree.d.ts.map +1 -0
  249. package/dist/src/shared/tree.js +134 -0
  250. package/dist/src/shared/tree.js.map +1 -0
  251. package/dist/src/slice/breakdown/index.d.ts +4 -0
  252. package/dist/src/slice/breakdown/index.d.ts.map +1 -1
  253. package/dist/src/slice/breakdown/types.d.ts +4 -0
  254. package/dist/src/slice/breakdown/types.d.ts.map +1 -1
  255. package/dist/src/slice/cohort/index.d.ts +24 -4
  256. package/dist/src/slice/cohort/index.d.ts.map +1 -1
  257. package/dist/src/slice/cohort/index.js +8 -4
  258. package/dist/src/slice/cohort/index.js.map +1 -1
  259. package/dist/src/slice/cohort/types.d.ts +11 -0
  260. package/dist/src/slice/cohort/types.d.ts.map +1 -1
  261. package/dist/src/slice/cohort/types.js +7 -0
  262. package/dist/src/slice/cohort/types.js.map +1 -1
  263. package/dist/src/slice/filter/index.d.ts +4 -0
  264. package/dist/src/slice/filter/index.d.ts.map +1 -1
  265. package/dist/src/slice/filter/types.d.ts +4 -0
  266. package/dist/src/slice/filter/types.d.ts.map +1 -1
  267. package/dist/src/slice/index.d.ts +736 -0
  268. package/dist/src/slice/index.d.ts.map +1 -1
  269. package/dist/src/slice/legend/index.d.ts +4 -0
  270. package/dist/src/slice/legend/index.d.ts.map +1 -1
  271. package/dist/src/slice/legend/types.d.ts +4 -0
  272. package/dist/src/slice/legend/types.d.ts.map +1 -1
  273. package/dist/src/slice/rail/index.d.ts +8 -4
  274. package/dist/src/slice/rail/index.d.ts.map +1 -1
  275. package/dist/src/slice/rail/index.js.map +1 -1
  276. package/dist/src/slice/range/index.d.ts +4 -0
  277. package/dist/src/slice/range/index.d.ts.map +1 -1
  278. package/dist/src/slice/range/types.d.ts +4 -0
  279. package/dist/src/slice/range/types.d.ts.map +1 -1
  280. package/dist/src/slice/search/index.d.ts +4 -0
  281. package/dist/src/slice/search/index.d.ts.map +1 -1
  282. package/dist/src/slice/search/types.d.ts +4 -0
  283. package/dist/src/slice/search/types.d.ts.map +1 -1
  284. package/dist/src/slice/summary/index.d.ts +4 -0
  285. package/dist/src/slice/summary/index.d.ts.map +1 -1
  286. package/dist/src/slice/summary/types.d.ts +4 -0
  287. package/dist/src/slice/summary/types.d.ts.map +1 -1
  288. package/dist/src/typography/code/index.js +1 -1
  289. package/dist/src/typography/code/index.js.map +1 -1
  290. package/dist/src/typography/code-block/index.js +7 -7
  291. package/dist/src/typography/code-block/index.js.map +1 -1
  292. package/dist/src/typography/heading/index.js +2 -2
  293. package/dist/src/typography/heading/index.js.map +1 -1
  294. package/dist/src/typography/list/index.js +1 -1
  295. package/dist/src/typography/list/index.js.map +1 -1
  296. package/dist/src/typography/mark/index.js +1 -1
  297. package/dist/src/typography/mark/index.js.map +1 -1
  298. package/dist/src/typography/note/index.js +1 -1
  299. package/dist/src/typography/note/index.js.map +1 -1
  300. package/dist/src/typography/numeric/index.d.ts +3 -308
  301. package/dist/src/typography/numeric/index.d.ts.map +1 -1
  302. package/dist/src/typography/numeric/index.js +4 -4
  303. package/dist/src/typography/numeric/index.js.map +1 -1
  304. package/dist/src/typography/numeric/types.d.ts +1 -154
  305. package/dist/src/typography/numeric/types.d.ts.map +1 -1
  306. package/dist/src/typography/text/index.js +1 -1
  307. package/dist/src/typography/text/index.js.map +1 -1
  308. package/dist/test/collections/matrix.examples.d.ts +7 -3
  309. package/dist/test/collections/matrix.examples.d.ts.map +1 -1
  310. package/dist/test/collections/matrix.examples.js +55 -24
  311. package/dist/test/collections/matrix.examples.js.map +1 -1
  312. package/dist/test/collections/paged-source.examples.d.ts +4 -0
  313. package/dist/test/collections/paged-source.examples.d.ts.map +1 -1
  314. package/dist/test/collections/paged-source.examples.js +98 -13
  315. package/dist/test/collections/paged-source.examples.js.map +1 -1
  316. package/dist/test/collections/plan.examples.d.ts +101 -26
  317. package/dist/test/collections/plan.examples.d.ts.map +1 -1
  318. package/dist/test/collections/plan.examples.js +1481 -755
  319. package/dist/test/collections/plan.examples.js.map +1 -1
  320. package/dist/test/collections/sheet-link.examples.d.ts +7 -0
  321. package/dist/test/collections/sheet-link.examples.d.ts.map +1 -0
  322. package/dist/test/collections/sheet-link.examples.js +41 -0
  323. package/dist/test/collections/sheet-link.examples.js.map +1 -0
  324. package/dist/test/collections/sheet-transactions.examples.d.ts +7 -0
  325. package/dist/test/collections/sheet-transactions.examples.d.ts.map +1 -0
  326. package/dist/test/collections/sheet-transactions.examples.js +58 -0
  327. package/dist/test/collections/sheet-transactions.examples.js.map +1 -0
  328. package/dist/test/collections/sheet.examples.d.ts +52 -5
  329. package/dist/test/collections/sheet.examples.d.ts.map +1 -1
  330. package/dist/test/collections/sheet.examples.js +500 -158
  331. package/dist/test/collections/sheet.examples.js.map +1 -1
  332. package/dist/test/collections/table.examples.d.ts +31 -3
  333. package/dist/test/collections/table.examples.d.ts.map +1 -1
  334. package/dist/test/collections/table.examples.js +188 -28
  335. package/dist/test/collections/table.examples.js.map +1 -1
  336. package/dist/test/contracts/editing.examples.d.ts +7 -0
  337. package/dist/test/contracts/editing.examples.d.ts.map +1 -0
  338. package/dist/test/contracts/editing.examples.js +58 -0
  339. package/dist/test/contracts/editing.examples.js.map +1 -0
  340. package/dist/test/layout/splitter.examples.d.ts.map +1 -1
  341. package/dist/test/layout/splitter.examples.js +3 -1
  342. package/dist/test/layout/splitter.examples.js.map +1 -1
  343. package/dist/test/platform/slice.examples.d.ts.map +1 -1
  344. package/dist/test/platform/slice.examples.js +1 -0
  345. package/dist/test/platform/slice.examples.js.map +1 -1
  346. package/dist/test/slice/slice.examples.d.ts.map +1 -1
  347. package/dist/test/slice/slice.examples.js +5 -2
  348. package/dist/test/slice/slice.examples.js.map +1 -1
  349. package/package.json +4 -3
  350. package/dist/src/collections/plan/data-forms.d.ts +0 -332
  351. package/dist/src/collections/plan/data-forms.d.ts.map +0 -1
  352. package/dist/src/collections/plan/data-forms.js +0 -212
  353. package/dist/src/collections/plan/data-forms.js.map +0 -1
@@ -11,8 +11,9 @@
11
11
  * positional surface, a `Dict<String, Row>` for a keyed one (#568). east-ui
12
12
  * declares the shape; whoever can actually fetch windows produces a value of
13
13
  * it. `Data.bindPaged` in `@elaraai/e3-ui` is the production implementation
14
- * (dataset windows over an e3 workspace) and {@link Paged.of} is the in-memory
15
- * one; neither is named here, and **nothing in this file imports e3**.
14
+ * (dataset windows over an e3 workspace) and {@link Paged.of} /
15
+ * {@link Paged.pinned} are the in-memory ones; e3 is never named here, and
16
+ * **nothing in this file imports e3**.
16
17
  *
17
18
  * # Why a variant, not a narrow struct
18
19
  *
@@ -28,7 +29,7 @@
28
29
  *
29
30
  * @packageDocumentation
30
31
  */
31
- import { East, Expr, ArrayType, BooleanType, DictType, FunctionType, IntegerType, OptionType, StringType, StructType, VariantType, variant, some, none, } from "@elaraai/east";
32
+ import { East, Expr, ArrayType, BooleanType, DictType, FunctionType, IntegerType, NullType, OptionType, SetType, StringType, StructType, VariantType, isTypeEqual, variant, some, none, } from "@elaraai/east";
32
33
  // ============================================================================
33
34
  // Seek — where a key lands in a source's row space
34
35
  // ============================================================================
@@ -123,10 +124,10 @@ export const SeekQueryType = VariantType({
123
124
  *
124
125
  * Parameterised on the COLLECTION, not on the row: a window of an `Array<R>`
125
126
  * dataset is an `Array<R>`, a window of a `Dict<K, V>` dataset is a
126
- * `Dict<K, V>`, and the Plan's canvas windows are `Dict<String, PlanRow>`
127
- * (#568). That is what `Data.bindPaged` already produces — it returns
128
- * `Option<T>` at the dataset's own type — so the contract matches the producer
129
- * instead of narrowing it to arrays.
127
+ * `Dict<K, V>`, and the Plan's canvas windows are its blocks — one
128
+ * `Array<PlanRow>` stream per series (#823). That is what `Data.bindPaged`
129
+ * already produces — it returns `Option<T>` at the dataset's own type — so the
130
+ * contract matches the producer instead of narrowing it to arrays.
130
131
  *
131
132
  * `page` and `total` follow the in-flight-is-`none` convention: a window still
132
133
  * being fetched reads `none` and the call re-fires when it lands, so the reads
@@ -134,6 +135,28 @@ export const SeekQueryType = VariantType({
134
135
  * source is exhausted at that offset — a reader that walks offsets terminates
135
136
  * on an empty window, never on `none`.
136
137
  *
138
+ * A window may also hold FEWER than `limit` elements while the source still
139
+ * has more: a source that bounds its pages serves what fits. e3 trims every
140
+ * page of a dataset to a byte budget — the same number of elements on every
141
+ * page of one dataset — so wide elements come back in short windows (#829).
142
+ * Only an EMPTY window means exhausted. Components never see a short window:
143
+ * {@link buildRowSource}'s derived `page` re-requests whatever a trimmed page
144
+ * left out, so every window it serves is whole.
145
+ *
146
+ * A window that CANNOT be read — a failed fetch, a page that does not decode —
147
+ * makes `page` THROW its reason; it never reads `none`, which means in flight,
148
+ * and a reader waiting on it would wait forever (#811). A component shows the
149
+ * failure where that window's rows would be and asks again when the user
150
+ * retries. A source may rate-limit repeat attempts (e3 relaunches a failed
151
+ * window on a read at least two seconds later), and an authoring error — a
152
+ * dataset that cannot be paged — keeps throwing. `seek` follows the same rule.
153
+ *
154
+ * A paged source names no snapshot, so a component reads each of its windows
155
+ * once: the same `id` serves the same rows for as long as the component holds
156
+ * it. A source whose content moves under one id — a dataset the view follows,
157
+ * a source the view writes — is a {@link PinnedSourceType}, which names the
158
+ * snapshot its windows, total and searches belong to.
159
+ *
137
160
  * @typeParam C - The collection type one window carries.
138
161
  * @param c - The collection type value.
139
162
  * @returns The concrete `StructType` of a paged source over `c`.
@@ -144,11 +167,13 @@ export const SeekQueryType = VariantType({
144
167
  * never re-renders when the source is swapped, and a window cache cannot key
145
168
  * itself. Two sources with the same `id` must serve the same rows.
146
169
  * @property page - `(offset, limit)` → that window's elements as a value of the
147
- * collection type; `none` while in flight, an EMPTY collection at exhaustion.
170
+ * collection type; `none` while in flight, an EMPTY collection at exhaustion,
171
+ * and possibly fewer than `limit` elements before it (see above). Throws when
172
+ * the window cannot be read.
148
173
  * @property total - The source's total element count, once known; `none` until then.
149
174
  * @property seek - The source's key-search capability ({@link SeekType}) —
150
175
  * `none` when the source is not key-ordered (an Array-backed source cannot
151
- * seek; there is nothing to binary-search).
176
+ * seek; there is nothing to binary-search). A search that fails throws.
152
177
  */
153
178
  export const PagedSourceType = (c) => StructType({
154
179
  id: StringType,
@@ -156,6 +181,46 @@ export const PagedSourceType = (c) => StructType({
156
181
  total: FunctionType([], OptionType(IntegerType)),
157
182
  seek: OptionType(FunctionType([SeekQueryType], OptionType(SeekRangeType))),
158
183
  });
184
+ /**
185
+ * A {@link PagedSourceType} that names its SNAPSHOT — the contract a component
186
+ * follows through changes to the source's content, and the one it edits.
187
+ *
188
+ * @remarks
189
+ * Every window, the total and every search belong to one snapshot, the
190
+ * source's `revision`, so rows from two snapshots never sit side by side. When
191
+ * the revision moves — the data was written, `refresh` was asked for — a
192
+ * component reads its windows again at the new one, and the rows it had stand
193
+ * in until each lands: the view never empties between two snapshots of its
194
+ * data (#821). An edit is checked against the snapshot it was drafted on, and
195
+ * the drafts retire once the source reads back at the snapshot the write made
196
+ * (#880) — which is why the Sheet and the Plan edit a paged source only
197
+ * through this contract.
198
+ *
199
+ * `Data.bindPaged` in `@elaraai/e3-ui` returns one, its revision the dataset's
200
+ * content hash, and {@link Paged.pinned} is the in-memory one.
201
+ *
202
+ * @typeParam C - The collection type one window carries.
203
+ * @param c - The collection type value.
204
+ * @returns The concrete `StructType` of a pinned source over `c`.
205
+ *
206
+ * @property id - The logical source's identity, as {@link PagedSourceType}'s;
207
+ * the rows it serves are its current revision's.
208
+ * @property page - As {@link PagedSourceType}'s, at the current revision.
209
+ * @property total - As {@link PagedSourceType}'s, at the current revision.
210
+ * @property seek - As {@link PagedSourceType}'s, at the current revision.
211
+ * @property revision - The snapshot the source serves; `none` while it is
212
+ * being found. A reader re-fires when the source moves to another.
213
+ * @property refresh - Move the source: `some(hash)` to that snapshot — after a
214
+ * write the view confirmed — or `none` to the one the source holds now.
215
+ */
216
+ export const PinnedSourceType = (c) => StructType({
217
+ id: StringType,
218
+ page: FunctionType([IntegerType, IntegerType], OptionType(c)),
219
+ total: FunctionType([], OptionType(IntegerType)),
220
+ seek: OptionType(FunctionType([SeekQueryType], OptionType(SeekRangeType))),
221
+ revision: FunctionType([], OptionType(StringType)),
222
+ refresh: FunctionType([OptionType(StringType)], NullType),
223
+ });
159
224
  // ============================================================================
160
225
  // The row-source variant — what components actually store
161
226
  // ============================================================================
@@ -163,10 +228,17 @@ export const PagedSourceType = (c) => StructType({
163
228
  * How a component's rows arrive: inline, or from a windowed source.
164
229
  *
165
230
  * @remarks
166
- * Both arms speak the same COLLECTION type — inline is the whole of it, paged
167
- * is a window of it — so a component that keys its rows (the Plan's
168
- * `Dict<String, PlanRow>`) and one that positions them (`Array<Row>`) share one
169
- * vocabulary without either shape leaking into the arm names (#568).
231
+ * Every arm speaks the same COLLECTION type — inline is the whole of it, the
232
+ * windowed arms a window of it — so a keyed source (a Sheet's
233
+ * `Dict<String, Row>`), a positional one (a Table's `Array<Row>`) and a
234
+ * composite one (the Plan's blocks, one row stream per series) share one
235
+ * vocabulary without any shape leaking into the arm names (#568).
236
+ *
237
+ * The two windowed arms differ in one thing: whether the source names its
238
+ * snapshot. `paged` is the released arm — exported UIs carry it, so it never
239
+ * changes — and a component reads each of its windows once; `pinned` names the
240
+ * revision its windows belong to, which is what lets a component follow the
241
+ * source as its content changes, and edit it.
170
242
  *
171
243
  * @typeParam C - The collection type.
172
244
  * @param c - The collection type value.
@@ -174,10 +246,12 @@ export const PagedSourceType = (c) => StructType({
174
246
  *
175
247
  * @property inline - The whole collection, already in hand.
176
248
  * @property paged - A {@link PagedSourceType} fetched a window at a time.
249
+ * @property pinned - A {@link PinnedSourceType} — windows at a named snapshot, followed from one to the next.
177
250
  */
178
251
  export const RowSourceType = (c) => VariantType({
179
252
  inline: c,
180
253
  paged: PagedSourceType(c),
254
+ pinned: PinnedSourceType(c),
181
255
  });
182
256
  /**
183
257
  * The element shape an ORDERED window carries: an index entry.
@@ -236,6 +310,34 @@ function keyTypeOf(t) {
236
310
  return type.key;
237
311
  return undefined;
238
312
  }
313
+ /** The members a {@link PinnedSourceType} adds to a paged source, at their types. */
314
+ const SNAPSHOT_MEMBERS = {
315
+ revision: FunctionType([], OptionType(StringType)),
316
+ refresh: FunctionType([OptionType(StringType)], NullType),
317
+ };
318
+ /**
319
+ * Whether a windowed source names its snapshot — `revision` and `refresh`,
320
+ * both, at {@link PinnedSourceType}'s types.
321
+ *
322
+ * @param fields - The source's struct fields
323
+ * @param label - Component name for the error message
324
+ * @returns Whether the source is pinned
325
+ * @throws Error when the source has one of the two without the other, or
326
+ * either at another type — it would build neither arm faithfully
327
+ */
328
+ function namesSnapshot(fields, label) {
329
+ const revision = fields["revision"];
330
+ const refresh = fields["refresh"];
331
+ if (revision === undefined && refresh === undefined)
332
+ return false;
333
+ if (revision !== undefined && refresh !== undefined
334
+ && isTypeEqual(revision, SNAPSHOT_MEMBERS.revision) && isTypeEqual(refresh, SNAPSHOT_MEMBERS.refresh)) {
335
+ return true;
336
+ }
337
+ const found = revision === undefined ? "only `refresh`" : refresh === undefined ? "only `revision`" : "them at other types";
338
+ throw new Error(`${label}: a paged source names its snapshot with both \`revision: () → Option<String>\` and ` +
339
+ `\`refresh: (Option<String>) → Null\`, as Data.bindPaged's handle and Paged.pinned do — this one has ${found}`);
340
+ }
239
341
  /**
240
342
  * Classify a component's rows prop at BUILD time — the one dispatch every
241
343
  * collection shares, so no component re-sniffs shapes of its own.
@@ -243,7 +345,9 @@ function keyTypeOf(t) {
243
345
  * Accepted shapes, in order:
244
346
  * - a collection expression / value (`Array<R>`, `Dict<K, V>`, `Set<R>`) ⇒ `inline`;
245
347
  * - a struct carrying `page` + `total` ⇒ `paged` (a {@link PagedSourceType},
246
- * or anything structurally matching it — `Data.bindPaged`'s handle);
348
+ * or anything structurally matching it), marked `pinned` when it also
349
+ * carries `revision` + `refresh` (a {@link PinnedSourceType} —
350
+ * `Data.bindPaged`'s handle, `Paged.pinned`);
247
351
  * - a struct carrying `read` ⇒ a whole-value bind handle, which resolves by
248
352
  * CALLING `read()` and recursing. The call becomes part of the surrounding
249
353
  * East expression, so it is evaluated inside the component's reactive render
@@ -252,7 +356,7 @@ function keyTypeOf(t) {
252
356
  *
253
357
  * @param data - The rows prop as the author passed it
254
358
  * @param label - Component name for the error message (`"Plan"`, `"Table"`)
255
- * @returns The resolved arm — see {@link ResolvedRowSource}
359
+ * @returns The resolved arm, retaining a live handle for invocation-time reads — see {@link ResolvedRowSource}
256
360
  * @throws Error when the expression is none of the accepted shapes
257
361
  */
258
362
  export function resolveRowSource(data, label) {
@@ -274,6 +378,8 @@ export function resolveRowSource(data, label) {
274
378
  throw new Error(`${label}: the paged source's \`page\` must return \`Option<Collection>\` ` +
275
379
  `(an Array, Dict or Set) — got ${JSON.stringify(page)}`);
276
380
  }
381
+ // A source that names its snapshot builds the `pinned` arm.
382
+ const pinned = namesSnapshot(fields, label);
277
383
  // An ORDERED window: positional, but every row carries its own key.
278
384
  // Recognised by the element's shape rather than announced by a flag,
279
385
  // so a source that serves index entries needs no second vocabulary.
@@ -281,19 +387,20 @@ export function resolveRowSource(data, label) {
281
387
  if (entry !== null) {
282
388
  return {
283
389
  kind: "ordered", source: expr,
284
- collectionType, elementType, keyType: entry.key, orderKeyType: entry.ik,
390
+ collectionType, elementType, keyType: entry.key, orderKeyType: entry.ik, pinned,
285
391
  };
286
392
  }
287
393
  return {
288
394
  kind: "paged", source: expr,
289
- collectionType, elementType, keyType: keyTypeOf(collectionType),
395
+ collectionType, elementType, keyType: keyTypeOf(collectionType), pinned,
290
396
  };
291
397
  }
292
398
  if (fields !== undefined && fields["read"] !== undefined) {
293
399
  // A whole-value bind handle (`Data.bind`) — read it here, in the
294
400
  // surrounding East expression, and resolve the result.
295
401
  const handle = expr;
296
- return resolveRowSource(handle.read(), label);
402
+ const resolved = resolveRowSource(handle.read(), label);
403
+ return resolved.kind === "inline" ? { ...resolved, live: expr } : resolved;
297
404
  }
298
405
  throw new Error(`${label}: rows must be a collection, a paged source (\`{ id, page, total }\` — e.g. Data.bindPaged), ` +
299
406
  `or a bound value (\`{ read }\` — e.g. Data.bind); got a ${t.type}`);
@@ -314,25 +421,40 @@ export function resolveRowSource(data, label) {
314
421
  * component's rows inherit the source's keys, and flattening at the boundary
315
422
  * would throw away exactly what makes a window's rows addressable (#568). A
316
423
  * positional component (Table) receives its `Array<Row>`; a keyed one (Plan) a
317
- * `Dict<String, R>`.
424
+ * `Dict<K, R>`.
318
425
  *
319
426
  * @typeParam Out - The component's own row COLLECTION type.
320
427
  * @param resolved - The output of {@link resolveRowSource}
321
428
  * @param outType - The component's row collection type
322
429
  * @param make - The source collection → the component's row collection
323
- * @param idSuffix - Appended to the paged source's `id` — see below
324
430
  * @returns The `RowSourceType(outType)` value to store in the IR
325
431
  *
326
432
  * @remarks
327
- * `idSuffix` exists because `make` is part of what the derived source SERVES.
328
- * {@link PagedSourceType} requires that two sources sharing an `id` serve the
329
- * same rows, and a component whose `make` changes — a Plan whose series list was
330
- * narrowed by a pick — now serves different rows from the same underlying
331
- * handle. Since a window cache keys on `id` alone and resident windows are never
332
- * re-read, an unsigned id leaves the previous rows on screen forever. A
333
- * component that can vary its `make` must pass a signature of what varied.
433
+ * The derived source keeps the handle's `id`: `make` is part of what it
434
+ * serves, and a component whose `make` changes (a Plan whose series list a
435
+ * pick narrowed) serves other rows from the same handle — which a renderer
436
+ * tells apart by comparing the derived source whole, closures included
437
+ * (`equivalentFor`, #809), not by its id (#822).
438
+ *
439
+ * The derived `page` serves WHOLE windows (#829). A handle may answer
440
+ * `(offset, limit)` with fewer than `limit` elements while it still has more —
441
+ * e3 trims every page to a byte budget — and every component addresses its
442
+ * windows at `w × size`, so a short window would silently drop its tail. The
443
+ * derived `page` therefore re-requests `(offset + served, limit − served)`
444
+ * until the window holds `limit` elements, the source is exhausted (an empty
445
+ * piece, or `offset + served` reaching a known `total()`), or a piece is still
446
+ * in flight — in which case the whole window reads `none` and re-fires when the
447
+ * piece lands. The pieces join in the collection's own order (arrays
448
+ * concatenate; dicts and sets union, their pieces being disjoint and ascending)
449
+ * and `make` runs once over the whole window. Each piece stays its own request,
450
+ * so a runtime caches them at the size the source chose to serve.
451
+ *
452
+ * A source that names its snapshot (`resolved.pinned`, a
453
+ * {@link PinnedSourceType}) builds the `pinned` arm, its `revision` and
454
+ * `refresh` carried through as they are; any other builds `paged`, which has
455
+ * neither. The derived `page` is the same either way.
334
456
  */
335
- export function buildRowSource(resolved, outType, make, idSuffix) {
457
+ export function buildRowSource(resolved, outType, make) {
336
458
  const sourceType = RowSourceType(outType);
337
459
  if (resolved.kind === "inline") {
338
460
  return East.value(variant("inline", make(resolved.rows)), sourceType);
@@ -340,48 +462,208 @@ export function buildRowSource(resolved, outType, make, idSuffix) {
340
462
  // `ordered` and `paged` build the same value: by the time `make` has run,
341
463
  // the rows are the component's own shape, and whatever the entry carried —
342
464
  // its key, its order key — is in there because the component's `make` put
343
- // it there. The distinction is a BUILD-time one, which is why there is no
344
- // third arm here for a renderer to match on.
345
- const handle = resolved.source;
465
+ // it there. The distinction is a BUILD-time one, which is why `ordered`
466
+ // has no arm of its own for a renderer to match on. What picks the arm is
467
+ // whether the source names its snapshot.
468
+ const handle = pagedHandleOf(resolved);
346
469
  // Erased locally: the window type is `Out`, but TS cannot see through the
347
470
  // generic to unify `Option<Out>`'s arms — the East type is what types it.
348
471
  const winType = outType;
472
+ // The WHOLE window of the source's own collection, then `make` over it
473
+ // (#829) — reified once, and shared with anything that must read the very
474
+ // windows a component reads (the Plan's editing, #880).
475
+ const window = buildPagedWindow(resolved);
349
476
  const page = East.function([IntegerType, IntegerType], OptionType(winType), ($, offset, limit) => {
350
- const empty = $.const(none, OptionType(winType));
351
- const win = $.let(handle.page(offset, limit));
352
- return win.match({
353
- some: ($, window) => {
354
- const built = $.let(make(window), outType);
355
- return $.const(some(built), OptionType(winType));
477
+ const read = $.const(window);
478
+ const result = $.let(none, OptionType(winType));
479
+ const whole = $.let(read(offset, limit));
480
+ $.match(whole, {
481
+ some: ($, entries) => {
482
+ const built = $.let(make(entries), outType);
483
+ $.assign(result, some(built));
356
484
  },
357
- none: (_$) => empty,
358
485
  });
486
+ return result;
359
487
  });
360
488
  // A source predating the contract carries no `id` / `seek`; fall back to a
361
489
  // constant identity (it still compares equal to itself) and no seek.
362
490
  const fields = structFields(Expr.type(resolved.source)) ?? {};
363
- const baseId = fields["id"] !== undefined ? handle.id : East.value("", StringType);
364
- const id = idSuffix !== undefined
365
- ? East.str `${baseId}#${East.value(idSuffix, StringType)}`
366
- : baseId;
491
+ const id = fields["id"] !== undefined ? handle.id : East.value("", StringType);
367
492
  const seek = fields["seek"] !== undefined
368
493
  ? handle.seek
369
494
  : East.value(none, OptionType(FunctionType([SeekQueryType], OptionType(SeekRangeType))));
370
- return East.value(variant("paged", { id, page, total: handle.total, seek }), sourceType);
495
+ if (!resolved.pinned) {
496
+ return East.value(variant("paged", { id, page, total: handle.total, seek }), sourceType);
497
+ }
498
+ const snapshot = resolved.source;
499
+ return East.value(variant("pinned", { id, page, total: handle.total, seek, revision: snapshot.revision, refresh: snapshot.refresh }), sourceType);
500
+ }
501
+ /** A resolved paged source's handle, at the contract's shape. */
502
+ function pagedHandleOf(resolved) {
503
+ return resolved.source;
504
+ }
505
+ /**
506
+ * The WHOLE window `(offset, limit)` of a paged source's own collection — the
507
+ * pieces a trimmed page left out asked for until the window holds `limit`
508
+ * elements, the source is exhausted, or a piece is in flight (the window then
509
+ * reads `none`, #829). What {@link buildRowSource}'s derived `page` maps its
510
+ * `make` over; a caller that must read the very windows a component reads —
511
+ * the Plan's editing reads its entries back (#880) — asks for them here, so
512
+ * each piece is the same request the component made.
513
+ *
514
+ * @param resolved - A resolved paged source ({@link resolveRowSource})
515
+ * @returns `(offset, limit)` → the window, `none` while a piece is in flight
516
+ */
517
+ export function buildPagedWindow(resolved) {
518
+ const handle = pagedHandleOf(resolved);
519
+ // The source's own collection — every piece of a window is a value of it.
520
+ // Its size and the in-order join of two pieces are reified ONCE, outside
521
+ // the block, per collection kind, then CALLED. Both are pure: a piece may
522
+ // be the very window object a runtime holds in its cache.
523
+ const pieceType = resolved.collectionType;
524
+ const pieceKind = pieceType.type;
525
+ const sizeOf = pieceKind === "Array"
526
+ ? East.function([pieceType], IntegerType, (_$, c) => c.length())
527
+ : pieceKind === "Dict"
528
+ ? East.function([pieceType], IntegerType, (_$, c) => c.size())
529
+ : East.function([pieceType], IntegerType, (_$, c) => c.size());
530
+ const joinOf = pieceKind === "Array"
531
+ ? East.function([pieceType, pieceType], pieceType, (_$, a, b) => a.concat(b))
532
+ : pieceKind === "Dict"
533
+ // Pieces are disjoint, so the conflict arm never runs.
534
+ ? East.function([pieceType, pieceType], pieceType, (_$, a, b) => a.union(b, (_$2, _mine, theirs) => theirs))
535
+ : East.function([pieceType, pieceType], pieceType, (_$, a, b) => a.union(b));
536
+ return East.function([IntegerType, IntegerType], OptionType(pieceType), ($, offset, limit) => {
537
+ // Bound ONCE: the handle may be a platform call (`Data.bindPaged(…)`
538
+ // passed inline), and this body reads it up to three times.
539
+ const src = $.const(handle);
540
+ const size = $.const(sizeOf);
541
+ const join = $.const(joinOf);
542
+ const result = $.let(none, OptionType(pieceType));
543
+ const first = $.let(src.page(offset, limit));
544
+ $.match(first, {
545
+ some: ($, head) => {
546
+ const window = $.let(head, pieceType);
547
+ const served = $.let(size(head), IntegerType);
548
+ const inFlight = $.let(false, BooleanType);
549
+ // An empty first window is exhaustion. A short one — the
550
+ // source trimmed it — asks for the rest, piece by piece.
551
+ $.while(served.greater(0n).and(() => served.less(limit)), ($, label) => {
552
+ // A known total says where the source ends: no piece past it.
553
+ const total = $.let(src.total());
554
+ $.match(total, {
555
+ some: ($, t) => {
556
+ $.if(offset.add(served).greaterEqual(t), ($) => { $.break(label); });
557
+ },
558
+ });
559
+ const next = $.let(src.page(offset.add(served), limit.subtract(served)));
560
+ $.match(next, {
561
+ none: ($) => {
562
+ // Still in flight: the whole window waits, and the
563
+ // piece's landing re-fires this evaluation.
564
+ $.assign(inFlight, true);
565
+ $.break(label);
566
+ },
567
+ some: ($, piece) => {
568
+ const n = $.let(size(piece), IntegerType);
569
+ $.if(n.equal(0n), ($) => { $.break(label); });
570
+ $.assign(window, join(window, piece));
571
+ $.assign(served, served.add(n));
572
+ },
573
+ });
574
+ });
575
+ $.if(inFlight.not(), ($) => { $.assign(result, some(window)); });
576
+ },
577
+ });
578
+ return result;
579
+ });
371
580
  }
372
581
  function createPagedOf(id, collection, options) {
373
- const collectionExpr = East.value(collection);
374
- if (Expr.type(collectionExpr).type === "Dict") {
375
- return keyedPagedOf(id, collectionExpr);
582
+ return createInMemory(id, collection, options, false);
583
+ }
584
+ function createPagedPinned(id, collection, options) {
585
+ return createInMemory(id, collection, options, true);
586
+ }
587
+ /**
588
+ * The in-memory source over a collection — {@link Paged.of}'s, or, `pinned`,
589
+ * {@link Paged.pinned}'s.
590
+ *
591
+ * @param id - The source's identity
592
+ * @param collection - The whole collection
593
+ * @param options - {@link PagedOfOptions}
594
+ * @param pinned - Whether the source names its snapshot
595
+ * @returns The source, at the collection it was given
596
+ * @throws {Error} When `pageLimit` is not a positive integer
597
+ */
598
+ function createInMemory(id, collection, options, pinned) {
599
+ const pageLimit = options?.pageLimit;
600
+ if (pageLimit !== undefined && !(Number.isInteger(pageLimit) && pageLimit > 0)) {
601
+ throw new Error(`${pinned ? "Paged.pinned" : "Paged.of"}: \`pageLimit\` must be a positive integer — the most elements one window serves (got ${pageLimit})`);
376
602
  }
377
- return arrayPagedOf(id, collectionExpr, options);
603
+ const collectionExpr = East.value(collection);
604
+ const collectionType = Expr.type(collectionExpr);
605
+ const keyed = collectionType.type === "Dict";
606
+ const sourceType = pinned ? PinnedSourceType(collectionType) : PagedSourceType(collectionType);
607
+ // Evaluate the input once when creating the source, then detach nested
608
+ // mutable values. Page and seek closures share that captured snapshot;
609
+ // passing a live read expression cannot make later pages drift. The copy
610
+ // is beast v2: v1 writes neither a recursive type nor a function, and an
611
+ // entry may be either (a Plan's entries, #822).
612
+ const capture = East.function([StringType, collectionType], sourceType, ($, snapshotId, input) => {
613
+ const snapshot = $.const(East.Blob.encodeBeast(input, "v2").decodeBeast(collectionType, "v2"), collectionType);
614
+ return keyed ? keyedPagedOf(snapshotId, snapshot, pageLimit, pinned)
615
+ : arrayPagedOf(snapshotId, snapshot, options, pinned);
616
+ });
617
+ return East.value(capture)(id, collectionExpr);
618
+ }
619
+ /**
620
+ * An in-memory source's lifecycle when it is pinned: its one snapshot is its
621
+ * id. Built as real `East.function`s over the id, never spliced.
622
+ *
623
+ * @param id - The source's identity — its snapshot
624
+ * @returns `revision` and `refresh`, at {@link PinnedSourceType}'s types
625
+ */
626
+ function fixedSnapshot(id) {
627
+ const revision = East.function([], OptionType(StringType), ($) => {
628
+ const snapshot = $.const(id, StringType);
629
+ return some(snapshot);
630
+ });
631
+ const refresh = East.function([OptionType(StringType)], NullType, ($, target) => {
632
+ const snapshot = $.const(id, StringType);
633
+ $.match(target, {
634
+ some: ($2, hash) => {
635
+ $2.if(hash.notEqual(snapshot), ($3) => {
636
+ $3.error("Paged.pinned: an immutable snapshot — create a source with the requested snapshot id");
637
+ });
638
+ },
639
+ none: () => { },
640
+ });
641
+ return null;
642
+ });
643
+ return { revision, refresh };
644
+ }
645
+ /**
646
+ * The window size a source actually serves for a requested `limit` — the
647
+ * request itself, or `pageLimit` when that is smaller. Reified once as a real
648
+ * East function (the `shared/reify` rule), so a page body CALLS it instead of
649
+ * branching on host state. `none` ⇒ the source never trims.
650
+ */
651
+ function servedLimitFn(pageLimit) {
652
+ const capValue = pageLimit === undefined ? none : some(BigInt(pageLimit));
653
+ return East.function([IntegerType], IntegerType, ($, limit) => {
654
+ const cap = $.const(capValue, OptionType(IntegerType));
655
+ return cap.match({
656
+ some: (_$, c) => limit.less(c).ifElse(() => limit, () => c),
657
+ none: (_$) => limit,
658
+ });
659
+ });
378
660
  }
379
661
  /**
380
662
  * The KEYED in-memory source — dict windows in canonical key order, with
381
663
  * `seek` derived from the keys themselves (a keyed collection needs no key
382
- * accessor: it already is one).
664
+ * accessor: it already is one). `pinned` adds its {@link fixedSnapshot}.
383
665
  */
384
- function keyedPagedOf(id, collection) {
666
+ function keyedPagedOf(id, collection, pageLimit, pinned) {
385
667
  const all = collection;
386
668
  const valueType = Expr.type(all).value;
387
669
  const dictType = DictType(StringType, valueType);
@@ -389,14 +671,16 @@ function keyedPagedOf(id, collection) {
389
671
  const entriesType = ArrayType(entryType);
390
672
  // Built OUTSIDE every block (an East macro inside one splices per use).
391
673
  const keyOfEntry = East.function([entryType], StringType, (_$, e) => e.key);
674
+ const servedLimit = servedLimitFn(pageLimit);
392
675
  const page = East.function([IntegerType, IntegerType], OptionType(dictType), ($, offset, limit) => {
393
676
  const src = $.const(all, dictType);
677
+ const served = $.const(servedLimit);
394
678
  // `toArray` walks the dictionary in key order, so the window a given
395
679
  // offset serves is the window a keyed dataset would serve.
396
680
  const entries = $.let(src.toArray(($2, v, k) => $2.const({ key: k, value: v }, entryType)), entriesType);
397
681
  const n = $.let(entries.length(), IntegerType);
398
682
  const start = $.let(offset.less(n).ifElse(() => offset, () => n), IntegerType);
399
- const rawEnd = $.let(offset.add(limit), IntegerType);
683
+ const rawEnd = $.let(offset.add(served(limit)), IntegerType);
400
684
  const end = $.let(rawEnd.less(n).ifElse(() => rawEnd, () => n), IntegerType);
401
685
  // In-memory windows are never in flight; an exhausted offset yields
402
686
  // the EMPTY window, which is how a walking reader terminates.
@@ -473,10 +757,14 @@ function keyedPagedOf(id, collection) {
473
757
  });
474
758
  });
475
759
  const seek = some(find);
476
- return East.value({ id, page, total, seek }, PagedSourceType(dictType));
760
+ const members = { id, page, total, seek };
761
+ return (pinned
762
+ ? East.value({ ...members, ...fixedSnapshot(id) }, PinnedSourceType(dictType))
763
+ : East.value(members, PagedSourceType(dictType)));
477
764
  }
478
- /** The POSITIONAL in-memory source — array windows in stream order. */
479
- function arrayPagedOf(id, rows, options) {
765
+ /** The POSITIONAL in-memory source — array windows in stream order.
766
+ * `pinned` adds its {@link fixedSnapshot}. */
767
+ function arrayPagedOf(id, rows, options, pinned) {
480
768
  const all = rows;
481
769
  const rowType = Expr.type(all).value;
482
770
  const rowsType = ArrayType(rowType);
@@ -486,11 +774,13 @@ function arrayPagedOf(id, rows, options) {
486
774
  const byFn = keyOf === undefined
487
775
  ? undefined
488
776
  : East.function([rowType], StringType, (_$, r) => keyOf(r));
777
+ const servedLimit = servedLimitFn(options?.pageLimit);
489
778
  const page = East.function([IntegerType, IntegerType], OptionType(rowsType), ($, offset, limit) => {
490
779
  const src = $.const(all, rowsType);
780
+ const served = $.const(servedLimit);
491
781
  const n = $.let(src.length(), IntegerType);
492
782
  const start = $.let(offset.less(n).ifElse(() => offset, () => n), IntegerType);
493
- const rawEnd = $.let(offset.add(limit), IntegerType);
783
+ const rawEnd = $.let(offset.add(served(limit)), IntegerType);
494
784
  const end = $.let(rawEnd.less(n).ifElse(() => rawEnd, () => n), IntegerType);
495
785
  // In-memory windows are never in flight; an exhausted offset yields
496
786
  // the EMPTY window, which is how a walking reader terminates.
@@ -567,11 +857,16 @@ function arrayPagedOf(id, rows, options) {
567
857
  // Two-step cast (the `Data.bindPaged` idiom): the members are built
568
858
  // against the row type recovered from the expression, which TS sees as the
569
859
  // erased `EastType` rather than the caller's `R`. The East-side type —
570
- // `PagedSourceType(rowsType)` — is what actually types the value.
571
- return East.value({ id, page, total, seek }, PagedSourceType(rowsType));
860
+ // `PagedSourceType(rowsType)`, or `PinnedSourceType(rowsType)` — is what
861
+ // actually types the value.
862
+ const members = { id, page, total, seek };
863
+ return (pinned
864
+ ? East.value({ ...members, ...fixedSnapshot(id) }, PinnedSourceType(rowsType))
865
+ : East.value(members, PagedSourceType(rowsType)));
572
866
  }
573
867
  /**
574
- * The `Paged` namespace — building a {@link PagedSourceType} without a server.
868
+ * The `Paged` namespace — building a {@link PagedSourceType} or a
869
+ * {@link PinnedSourceType} without a server.
575
870
  *
576
871
  * @remarks
577
872
  * The contract itself is what components consume; this namespace is the
@@ -581,15 +876,19 @@ function arrayPagedOf(id, rows, options) {
581
876
  export const Paged = {
582
877
  /** Build an in-memory paged source over a collection already in hand. */
583
878
  of: createPagedOf,
879
+ /** Build an in-memory PINNED source — `of`, naming its one snapshot, for a fixture a view edits or follows. */
880
+ pinned: createPagedPinned,
584
881
  /** East types — the contract, for `$.const` / `$.let` annotations. */
585
882
  Types: {
586
883
  /** A windowed row source over a collection type. */
587
884
  Source: PagedSourceType,
885
+ /** A windowed row source that names its snapshot, over a collection type. */
886
+ PinnedSource: PinnedSourceType,
588
887
  /** Where a key query landed in a source's row order. */
589
888
  SeekRange: SeekRangeType,
590
889
  /** A key query — exact literal, String prefix, leading struct fields, or a range. */
591
890
  SeekQuery: SeekQueryType,
592
- /** How a component's rows arrive (inline / paged), at a collection type. */
891
+ /** How a component's rows arrive (inline / paged / pinned), at a collection type. */
593
892
  RowSource: RowSourceType,
594
893
  },
595
894
  };