@noy-db/hub 0.7.0 → 0.8.0-pre.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 (616) hide show
  1. package/CHANGELOG.md +105 -14
  2. package/LICENSE +202 -21
  3. package/NOTICE +1 -0
  4. package/README.md +27 -10
  5. package/codemods/0.8.0-pre.json +61 -0
  6. package/dist/api-GVYEKSJE.js +19 -0
  7. package/dist/as/index.js +11 -11
  8. package/dist/at/index.js +9 -9
  9. package/dist/attestation/index.js +20 -20
  10. package/dist/{backup-IJTB4S4M.js → backup-7TFRVFLH.js} +21 -15
  11. package/dist/backup-7TFRVFLH.js.map +1 -0
  12. package/dist/blobs/index.js +13 -13
  13. package/dist/broker/index.js +12 -12
  14. package/dist/by/index.js +2 -2
  15. package/dist/cargo/index.js +39 -36
  16. package/dist/{chunk-7O3Y4PPD.js → chunk-244VXYPG.js} +140 -16
  17. package/dist/chunk-244VXYPG.js.map +1 -0
  18. package/dist/{chunk-BUE2D7PX.js → chunk-32YV3TJT.js} +2 -2
  19. package/dist/{chunk-3RCJJBNR.js → chunk-3ELRMVJK.js} +12 -12
  20. package/dist/{chunk-6GMZ7HEE.js → chunk-3F7GLNAE.js} +6 -6
  21. package/dist/{chunk-2Y4IMKA6.js → chunk-3H5KLDZN.js} +4 -4
  22. package/dist/{chunk-VTFVLCUH.js → chunk-3JWHAR6E.js} +2 -2
  23. package/dist/{chunk-YMUFFJCO.js → chunk-3TDUWVWB.js} +5 -2
  24. package/dist/chunk-3TDUWVWB.js.map +1 -0
  25. package/dist/{chunk-JBDFMYKQ.js → chunk-4AF3TUMP.js} +2 -2
  26. package/dist/{chunk-ZORTV6NH.js → chunk-4I522ERM.js} +88 -4
  27. package/dist/chunk-4I522ERM.js.map +1 -0
  28. package/dist/{chunk-MUM7IANK.js → chunk-4MXI3ODD.js} +4 -4
  29. package/dist/chunk-4MXI3ODD.js.map +1 -0
  30. package/dist/{chunk-BLN54W3L.js → chunk-5FDLC6G7.js} +56 -3
  31. package/dist/chunk-5FDLC6G7.js.map +1 -0
  32. package/dist/chunk-5O7D6ZTW.js +53 -0
  33. package/dist/chunk-5O7D6ZTW.js.map +1 -0
  34. package/dist/{chunk-J6FXAEUG.js → chunk-6B3VNR4P.js} +12 -12
  35. package/dist/{chunk-TUFM4UZP.js → chunk-6K7DPM7D.js} +2 -2
  36. package/dist/{chunk-K6LHRFBL.js → chunk-6LMQ5R26.js} +7 -7
  37. package/dist/{chunk-SPXXX5E4.js → chunk-6LNL42F5.js} +94 -32
  38. package/dist/chunk-6LNL42F5.js.map +1 -0
  39. package/dist/{chunk-XLMEZIAJ.js → chunk-6OWXYZK2.js} +2 -2
  40. package/dist/{chunk-BOWNCWZT.js → chunk-6QSQQLSE.js} +3 -3
  41. package/dist/chunk-7JE7BDYJ.js +1558 -0
  42. package/dist/chunk-7JE7BDYJ.js.map +1 -0
  43. package/dist/{chunk-KG7TZKZG.js → chunk-7VCXZDA4.js} +5 -5
  44. package/dist/chunk-7VCXZDA4.js.map +1 -0
  45. package/dist/{chunk-NQKIUNW5.js → chunk-A6IJU4TK.js} +2 -2
  46. package/dist/{chunk-BM5TWITZ.js → chunk-ANWPSTZU.js} +38 -9
  47. package/dist/chunk-ANWPSTZU.js.map +1 -0
  48. package/dist/{chunk-QIZPZMUI.js → chunk-AOWGXU6D.js} +2 -2
  49. package/dist/{chunk-CUZLNGD5.js → chunk-B2YY6WDD.js} +1 -1
  50. package/dist/chunk-B2YY6WDD.js.map +1 -0
  51. package/dist/{chunk-SFAWCTEM.js → chunk-B57AFBY2.js} +2 -2
  52. package/dist/{chunk-GPEQUXHL.js → chunk-B6SJIBQK.js} +4 -4
  53. package/dist/chunk-BJ2IXQ5Q.js +100 -0
  54. package/dist/chunk-BJ2IXQ5Q.js.map +1 -0
  55. package/dist/{chunk-Q2PU57HC.js → chunk-BK2HHQGN.js} +2 -2
  56. package/dist/chunk-BKT776R5.js +1 -0
  57. package/dist/{chunk-KUX3OB6D.js → chunk-BP6DARYA.js} +33 -5
  58. package/dist/chunk-BP6DARYA.js.map +1 -0
  59. package/dist/{chunk-FXBIP2WD.js → chunk-BUDAGWL5.js} +6 -6
  60. package/dist/chunk-CF5UU4S7.js +757 -0
  61. package/dist/chunk-CF5UU4S7.js.map +1 -0
  62. package/dist/{chunk-BOET4NA4.js → chunk-CKNGS5MH.js} +3 -3
  63. package/dist/chunk-CY2CYVWZ.js +41 -0
  64. package/dist/chunk-CY2CYVWZ.js.map +1 -0
  65. package/dist/{chunk-2577DKXB.js → chunk-DC5QVPV7.js} +2 -2
  66. package/dist/chunk-DC5QVPV7.js.map +1 -0
  67. package/dist/{chunk-YSRUYAVU.js → chunk-DE4DH2RZ.js} +2 -2
  68. package/dist/{chunk-6I2RQCET.js → chunk-DF73H4UN.js} +4 -4
  69. package/dist/{chunk-4GVM6QZA.js → chunk-ENAG3IY7.js} +2 -2
  70. package/dist/chunk-ENAG3IY7.js.map +1 -0
  71. package/dist/{chunk-K7NTTA6P.js → chunk-EOVMT6TB.js} +2 -2
  72. package/dist/chunk-F24ZZTHQ.js +193 -0
  73. package/dist/chunk-F24ZZTHQ.js.map +1 -0
  74. package/dist/chunk-F2TDU7MQ.js +456 -0
  75. package/dist/chunk-F2TDU7MQ.js.map +1 -0
  76. package/dist/chunk-F6NCB7KT.js +13 -0
  77. package/dist/chunk-F6NCB7KT.js.map +1 -0
  78. package/dist/{chunk-SPYWXKJN.js → chunk-F7XGHL5O.js} +3 -3
  79. package/dist/chunk-F7XGHL5O.js.map +1 -0
  80. package/dist/{chunk-XTXOCYOJ.js → chunk-FCKWQE2H.js} +5 -5
  81. package/dist/{chunk-U4FOQDY4.js → chunk-FH2L2E34.js} +4 -4
  82. package/dist/chunk-FH2L2E34.js.map +1 -0
  83. package/dist/{chunk-4OZNXTO4.js → chunk-FN5EBVTQ.js} +2 -2
  84. package/dist/chunk-FQ54DWUQ.js +270 -0
  85. package/dist/chunk-FQ54DWUQ.js.map +1 -0
  86. package/dist/{chunk-NKHRDYOR.js → chunk-FQ5VRHIB.js} +4 -4
  87. package/dist/{chunk-FAHMLERK.js → chunk-FW22ZCXT.js} +2 -2
  88. package/dist/chunk-FXQBGFOX.js +524 -0
  89. package/dist/chunk-FXQBGFOX.js.map +1 -0
  90. package/dist/{chunk-AO42KJO6.js → chunk-GF5BZ7D4.js} +45 -9
  91. package/dist/chunk-GF5BZ7D4.js.map +1 -0
  92. package/dist/{chunk-TRGTKX5E.js → chunk-GHLTDZRS.js} +5 -3
  93. package/dist/{chunk-TRGTKX5E.js.map → chunk-GHLTDZRS.js.map} +1 -1
  94. package/dist/{chunk-5WDXHNSW.js → chunk-HBPPGWUM.js} +5 -5
  95. package/dist/chunk-HBPPGWUM.js.map +1 -0
  96. package/dist/{chunk-KTFRCC5C.js → chunk-HBRLNOHL.js} +82 -8
  97. package/dist/chunk-HBRLNOHL.js.map +1 -0
  98. package/dist/chunk-HLEK5VAN.js +275 -0
  99. package/dist/chunk-HLEK5VAN.js.map +1 -0
  100. package/dist/{chunk-RMS3NEA3.js → chunk-HT2AHGQI.js} +2 -2
  101. package/dist/{chunk-CHJBLCOG.js → chunk-HVSCEIYL.js} +7 -7
  102. package/dist/{chunk-UXWB3FPF.js → chunk-I3JDRCMW.js} +150 -22
  103. package/dist/chunk-I3JDRCMW.js.map +1 -0
  104. package/dist/{chunk-GY4WYNNG.js → chunk-ID6Z6E66.js} +2 -2
  105. package/dist/{chunk-EYBQEIUY.js → chunk-IEX7NK2K.js} +3 -3
  106. package/dist/{chunk-KUJDV5QH.js → chunk-ISZQJBML.js} +5 -5
  107. package/dist/{chunk-LWBCUYZW.js → chunk-IT22PWKV.js} +2 -2
  108. package/dist/{chunk-EQEYOJBM.js → chunk-IUWCVHQ7.js} +3 -3
  109. package/dist/chunk-IUWCVHQ7.js.map +1 -0
  110. package/dist/{chunk-7V4TP3AF.js → chunk-J6UOHJY5.js} +4 -4
  111. package/dist/{chunk-JMGDHKQF.js → chunk-JCN44NCY.js} +2 -2
  112. package/dist/{chunk-CARABLSQ.js → chunk-K6W7BVZ6.js} +3 -3
  113. package/dist/{chunk-77K4PG6P.js → chunk-KJFW62P4.js} +29 -11
  114. package/dist/chunk-KJFW62P4.js.map +1 -0
  115. package/dist/{chunk-453F654R.js → chunk-L7DOS3TE.js} +2 -2
  116. package/dist/chunk-LGNDFRYC.js +23 -0
  117. package/dist/chunk-LGNDFRYC.js.map +1 -0
  118. package/dist/{chunk-WEQEYLCS.js → chunk-LGRLME4M.js} +7 -7
  119. package/dist/{chunk-RMF67KCG.js → chunk-LQ4ZHOKP.js} +2 -2
  120. package/dist/{chunk-ZVO4QVPD.js → chunk-M522UYZ2.js} +5 -5
  121. package/dist/{chunk-G56KBWFY.js → chunk-M7NI3IWE.js} +45 -12
  122. package/dist/chunk-M7NI3IWE.js.map +1 -0
  123. package/dist/chunk-MDWKM2XC.js +293 -0
  124. package/dist/chunk-MDWKM2XC.js.map +1 -0
  125. package/dist/chunk-MKXXMNJH.js +204 -0
  126. package/dist/chunk-MKXXMNJH.js.map +1 -0
  127. package/dist/{chunk-SKGMN35F.js → chunk-MLPHLJIQ.js} +3 -3
  128. package/dist/chunk-MLPHLJIQ.js.map +1 -0
  129. package/dist/{chunk-2N4GY74U.js → chunk-MTL7MXGR.js} +6 -6
  130. package/dist/{chunk-TFR7DSOV.js → chunk-MVZ2TPVP.js} +2 -2
  131. package/dist/{chunk-CPWQB3N5.js → chunk-N5DNADLQ.js} +7 -7
  132. package/dist/{chunk-ELNXJMGB.js → chunk-NNARWO72.js} +6 -6
  133. package/dist/{chunk-PT43Y56J.js → chunk-NRT6QZQB.js} +102 -16
  134. package/dist/chunk-NRT6QZQB.js.map +1 -0
  135. package/dist/{chunk-QIBVIOXO.js → chunk-NWHBJQAL.js} +99 -8
  136. package/dist/chunk-NWHBJQAL.js.map +1 -0
  137. package/dist/chunk-NYMLINRP.js +28 -0
  138. package/dist/chunk-NYMLINRP.js.map +1 -0
  139. package/dist/{chunk-7MG7SJT7.js → chunk-ODVPTDPK.js} +1 -1
  140. package/dist/chunk-ODVPTDPK.js.map +1 -0
  141. package/dist/chunk-ORDURZBU.js +461 -0
  142. package/dist/chunk-ORDURZBU.js.map +1 -0
  143. package/dist/{chunk-6YXCPL4T.js → chunk-PDCG6PR5.js} +2 -2
  144. package/dist/{chunk-REDOOY2Q.js → chunk-PSIV7CJV.js} +2 -2
  145. package/dist/{chunk-NA2NIJZI.js → chunk-QBBUZ3I3.js} +126 -19
  146. package/dist/chunk-QBBUZ3I3.js.map +1 -0
  147. package/dist/{chunk-5VCRVKOZ.js → chunk-QOOUUR4A.js} +6 -4
  148. package/dist/{chunk-5VCRVKOZ.js.map → chunk-QOOUUR4A.js.map} +1 -1
  149. package/dist/{chunk-ANATMFIO.js → chunk-QWFHIOD2.js} +5 -5
  150. package/dist/{chunk-X2DLENDQ.js → chunk-QWGU66MP.js} +3 -3
  151. package/dist/chunk-RHVMZSUU.js +7 -0
  152. package/dist/chunk-RHVMZSUU.js.map +1 -0
  153. package/dist/{chunk-BSFNRLK2.js → chunk-S57HTZ6X.js} +5 -5
  154. package/dist/{chunk-SZJ3DLZA.js → chunk-SEE332EV.js} +2 -2
  155. package/dist/{chunk-YYP7WSEB.js → chunk-SI3V7D4G.js} +2 -2
  156. package/dist/{chunk-UGTBXF5U.js → chunk-SOIQ4MM4.js} +3 -3
  157. package/dist/chunk-SUZKW6UR.js +25 -0
  158. package/dist/chunk-SUZKW6UR.js.map +1 -0
  159. package/dist/{chunk-VXGSWL6L.js → chunk-SYVZW77S.js} +6 -6
  160. package/dist/{chunk-6GOE5PGN.js → chunk-T2LBQ5DV.js} +883 -227
  161. package/dist/chunk-T2LBQ5DV.js.map +1 -0
  162. package/dist/chunk-T52GX4SA.js +215 -0
  163. package/dist/chunk-T52GX4SA.js.map +1 -0
  164. package/dist/{chunk-MVMXZYXT.js → chunk-TKUOEU2F.js} +2 -2
  165. package/dist/chunk-TOPYQ6GA.js +31 -0
  166. package/dist/chunk-TOPYQ6GA.js.map +1 -0
  167. package/dist/{chunk-SMH6SRX7.js → chunk-TROR3ALR.js} +2 -2
  168. package/dist/{chunk-2C3AQB46.js → chunk-UD5EVGZ7.js} +42 -389
  169. package/dist/chunk-UD5EVGZ7.js.map +1 -0
  170. package/dist/{chunk-KJIMGIWO.js → chunk-UPD5NWI5.js} +65 -17
  171. package/dist/chunk-UPD5NWI5.js.map +1 -0
  172. package/dist/{chunk-SL3JAAKN.js → chunk-UYB234ZR.js} +30 -2
  173. package/dist/chunk-UYB234ZR.js.map +1 -0
  174. package/dist/chunk-VHM7HCKB.js +220 -0
  175. package/dist/chunk-VHM7HCKB.js.map +1 -0
  176. package/dist/{chunk-DGOZ4UZD.js → chunk-VNNRUGMM.js} +2 -2
  177. package/dist/{chunk-HDOU6I2Y.js → chunk-VNPEWFPD.js} +4 -4
  178. package/dist/{chunk-L7CIOOBW.js → chunk-WNJ4AVGE.js} +2 -2
  179. package/dist/{chunk-FMZY5Q6L.js → chunk-WX5ADGTZ.js} +3 -3
  180. package/dist/{chunk-VXANTWBJ.js → chunk-WY2XXTET.js} +92 -6
  181. package/dist/{chunk-VXANTWBJ.js.map → chunk-WY2XXTET.js.map} +1 -1
  182. package/dist/{chunk-6X6SEMEE.js → chunk-X25NWUZB.js} +33 -8
  183. package/dist/chunk-X25NWUZB.js.map +1 -0
  184. package/dist/{chunk-Q7HZX3IF.js → chunk-X4773RBR.js} +2 -2
  185. package/dist/chunk-XBKH4FOC.js +195 -0
  186. package/dist/chunk-XBKH4FOC.js.map +1 -0
  187. package/dist/{chunk-ANSIZYEG.js → chunk-YNIUIERI.js} +3 -3
  188. package/dist/chunk-YSK2CU2Q.js +936 -0
  189. package/dist/chunk-YSK2CU2Q.js.map +1 -0
  190. package/dist/{chunk-UXVWEX5L.js → chunk-Z5HTYMMQ.js} +6 -2
  191. package/dist/chunk-Z5HTYMMQ.js.map +1 -0
  192. package/dist/{chunk-RRNRNANG.js → chunk-ZE4CHIED.js} +66 -75
  193. package/dist/chunk-ZE4CHIED.js.map +1 -0
  194. package/dist/chunk-ZSX4S5XB.js +1289 -0
  195. package/dist/chunk-ZSX4S5XB.js.map +1 -0
  196. package/dist/{chunk-BZ3534YM.js → chunk-ZUVBC32X.js} +2 -2
  197. package/dist/{chunk-L742JPKZ.js → chunk-ZXABPVM6.js} +6 -6
  198. package/dist/classified/index.js +5 -5
  199. package/dist/{classified-marker-LI2SF2E4.js → classified-marker-W3USLL5M.js} +3 -3
  200. package/dist/collection-facade-QU6IJBN3.js +48 -0
  201. package/dist/{computed-VPOKQK4N.js → computed-TPFBX7U4.js} +3 -3
  202. package/dist/consent/index.js +9 -9
  203. package/dist/cover/index.js +10 -10
  204. package/dist/coverage/index.js +306 -0
  205. package/dist/coverage/index.js.map +1 -0
  206. package/dist/custody/index.js +12 -12
  207. package/dist/{dead-filter-TSWZQRR5.js → dead-filter-U2UYKSZL.js} +2 -2
  208. package/dist/debug/index.js +1 -1
  209. package/dist/{delegation-OVIFFPG4.js → delegation-S3AEPW2T.js} +11 -11
  210. package/dist/derivations/index.js +25 -22
  211. package/dist/derive-OX7F6TGG.js +21 -0
  212. package/dist/directory/index.js +10 -10
  213. package/dist/{dispatch-WC6X2M6C.js → dispatch-3CQDJV5Q.js} +5 -5
  214. package/dist/{dispatch-S73TK3IY.js → dispatch-RH4LHKBH.js} +21 -21
  215. package/dist/{enclave-KJOTOH7X.js → enclave-27SMF6GF.js} +15 -9
  216. package/dist/executor-B6SRO2QS.js +36 -0
  217. package/dist/executor-BI5CHWRY.js +8 -0
  218. package/dist/executor-RBKM5W5K.js +8 -0
  219. package/dist/export-accessible-VMA3YM5V.js +22 -0
  220. package/dist/extract-partition-NF7PXGPP.js +37 -0
  221. package/dist/{fanout-sidecar-WUFXNYP4.js → fanout-sidecar-OT6RYLOL.js} +9 -9
  222. package/dist/find-LZMP26KF.js +10 -0
  223. package/dist/forget/index.js +9 -9
  224. package/dist/guards/index.js +4 -4
  225. package/dist/history/index.js +13 -13
  226. package/dist/i18n/index.js +14 -14
  227. package/dist/index.d.ts +29 -6
  228. package/dist/index.js +999 -191
  229. package/dist/index.js.map +1 -1
  230. package/dist/indexing/index.js +13 -5
  231. package/dist/indexing/index.js.map +1 -1
  232. package/dist/introspection/index.js +11 -11
  233. package/dist/issue-IHARXWW5.js +18 -0
  234. package/dist/ivf-flat-UZ6DQDLS.js +314 -0
  235. package/dist/ivf-flat-UZ6DQDLS.js.map +1 -0
  236. package/dist/{json-schema-V3M5MUCM.js → json-schema-3SHX72CC.js} +2 -1
  237. package/dist/json-schema-3SHX72CC.js.map +1 -0
  238. package/dist/kernel/collection-config.d.ts +4 -20
  239. package/dist/kernel/collection.d.ts +29 -8
  240. package/dist/kernel/enclave/crypto.d.ts +16 -0
  241. package/dist/kernel/enclave/index.d.ts +1 -1
  242. package/dist/kernel/enclave/record-keys/record-codec.d.ts +25 -0
  243. package/dist/kernel/errors.d.ts +227 -36
  244. package/dist/kernel/generation-map.d.ts +48 -0
  245. package/dist/kernel/memory-store.d.ts +35 -7
  246. package/dist/kernel/noydb.d.ts +6 -1
  247. package/dist/kernel/present-cache.d.ts +58 -0
  248. package/dist/kernel/query/all.d.ts +7 -0
  249. package/dist/kernel/query/builder.d.ts +601 -270
  250. package/dist/kernel/query/civil-date.d.ts +64 -0
  251. package/dist/kernel/query/cursor.d.ts +40 -0
  252. package/dist/kernel/query/distinct-key.d.ts +98 -0
  253. package/dist/kernel/query/hydration.d.ts +76 -0
  254. package/dist/kernel/query/index.d.ts +31 -15
  255. package/dist/kernel/query/internal/core.d.ts +30 -0
  256. package/dist/kernel/query/internal/hooks.d.ts +22 -0
  257. package/dist/kernel/query/internal/maintenance.d.ts +43 -0
  258. package/dist/kernel/query/live/incremental.d.ts +314 -0
  259. package/dist/kernel/query/live/index.d.ts +22 -0
  260. package/dist/kernel/query/live/install.d.ts +2 -0
  261. package/dist/kernel/query/{live.d.ts → live/live.d.ts} +39 -9
  262. package/dist/kernel/query/live/methods.d.ts +107 -0
  263. package/dist/kernel/query/predicate.d.ts +119 -1
  264. package/dist/kernel/query/reduce/date-trunc.d.ts +75 -0
  265. package/dist/kernel/query/reduce/index.d.ts +36 -0
  266. package/dist/kernel/query/reduce/install.d.ts +2 -0
  267. package/dist/kernel/query/reduce/methods.d.ts +259 -0
  268. package/dist/kernel/query/reduce/scan-methods.d.ts +121 -0
  269. package/dist/kernel/query/relate/explain.d.ts +199 -0
  270. package/dist/kernel/query/relate/index.d.ts +48 -0
  271. package/dist/kernel/query/relate/install.d.ts +2 -0
  272. package/dist/kernel/query/relate/join-on.d.ts +126 -0
  273. package/dist/kernel/query/relate/join-reduce.d.ts +16 -0
  274. package/dist/kernel/query/relate/join.d.ts +452 -0
  275. package/dist/kernel/query/relate/methods.d.ts +426 -0
  276. package/dist/kernel/query/relate/partition.d.ts +87 -0
  277. package/dist/kernel/query/relate/scan-methods.d.ts +163 -0
  278. package/dist/kernel/query/relate/traverse.d.ts +109 -0
  279. package/dist/kernel/query/scan-builder.d.ts +79 -176
  280. package/dist/kernel/query/sort-key.d.ts +52 -0
  281. package/dist/kernel/tx-write-gate.d.ts +77 -0
  282. package/dist/kernel/types.d.ts +71 -3
  283. package/dist/kernel/validation.d.ts +27 -10
  284. package/dist/kernel/vault.d.ts +9 -4
  285. package/dist/kernel/via/compose.d.ts +3 -0
  286. package/dist/kernel/via/index.d.ts +56 -0
  287. package/dist/kernel/via/pipeline.d.ts +48 -0
  288. package/dist/{ledger-F33ZVMTK.js → ledger-KZPCMVZY.js} +11 -11
  289. package/dist/liberate-GRLCZMW4.js +24 -0
  290. package/dist/link-set-JCMJ7ULP.js +30 -0
  291. package/dist/{managed-secret-PQYKSR6T.js → managed-secret-S3MQ6SH6.js} +10 -10
  292. package/dist/materialized-views/index.js +30 -23
  293. package/dist/{migrate-cek-5ENH64AA.js → migrate-cek-V7GKJZ7O.js} +2 -2
  294. package/dist/money/index.js +5 -5
  295. package/dist/noydb-SWQJ3MZS.js +97 -0
  296. package/dist/overlay-views/index.js +4 -4
  297. package/dist/periods/index.js +12 -12
  298. package/dist/pod/index.js +75 -68
  299. package/dist/{pod-handle-Q736KYON.js → pod-handle-WGDFB4C2.js} +9 -9
  300. package/dist/policy/index.js +10 -10
  301. package/dist/port/with/blob-strategy.d.ts +9 -0
  302. package/dist/port/with/collection-options.d.ts +5 -0
  303. package/dist/port/with/coverage-strategy.d.ts +83 -0
  304. package/dist/port/with/service-bus.d.ts +46 -0
  305. package/dist/port/with/strategies.d.ts +2 -0
  306. package/dist/portability/index.js +8 -8
  307. package/dist/{post-register-LNE2FE2A.js → post-register-SLSXIPBV.js} +6 -6
  308. package/dist/query/all/index.js +31 -0
  309. package/dist/query/index.js +20 -32
  310. package/dist/query/live/index.js +21 -0
  311. package/dist/query/live/index.js.map +1 -0
  312. package/dist/query/reduce/index.js +33 -0
  313. package/dist/query/reduce/index.js.map +1 -0
  314. package/dist/query/relate/index.js +56 -0
  315. package/dist/query/relate/index.js.map +1 -0
  316. package/dist/reduce/index.js +72 -18
  317. package/dist/reduce/index.js.map +1 -1
  318. package/dist/register-F3OYE7MJ.js +31 -0
  319. package/dist/registry-CILQFXFK.js +8 -0
  320. package/dist/registry-XNNYQVKV.js +8 -0
  321. package/dist/registry-YCDZV3VW.js +35 -0
  322. package/dist/registry-ZGYS33TE.js +24 -0
  323. package/dist/request-withdrawal-Z5NC6TXT.js +30 -0
  324. package/dist/{reveal-67RDQRIN.js → reveal-4BCKPEC2.js} +6 -6
  325. package/dist/revoke-ZL62WC2D.js +23 -0
  326. package/dist/satellites/index.js +1 -1
  327. package/dist/schema-update/index.js +2 -2
  328. package/dist/sealed-record/index.js +12 -12
  329. package/dist/search/index.js +11 -5
  330. package/dist/{seed-G42JYN56.js → seed-4JS2RJ4A.js} +12 -12
  331. package/dist/sequence/index.js +9 -9
  332. package/dist/session/index.js +9 -9
  333. package/dist/shadow/index.js +2 -2
  334. package/dist/signer-MBZZMXTC.js +24 -0
  335. package/dist/snapshots/index.js +10 -10
  336. package/dist/{stale-DDL7WQEZ.js → stale-TJ3DYZMQ.js} +10 -10
  337. package/dist/storage-SVMKRGCW.js +22 -0
  338. package/dist/{store-coordination-provider-MEQ2MJQG.js → store-coordination-provider-2O2VUHKW.js} +10 -10
  339. package/dist/sync/index.js +10 -10
  340. package/dist/team/index.js +17 -15
  341. package/dist/tiers/index.js +13 -11
  342. package/dist/to/index.js +2 -2
  343. package/dist/transactions/index.js +3 -3
  344. package/dist/util/index.js +1 -1
  345. package/dist/vault-head/index.js +8 -8
  346. package/dist/{verify-KOV2G5D6.js → verify-RVT67IMH.js} +5 -5
  347. package/dist/via/blob/index.d.ts +1 -0
  348. package/dist/via/geo/binding.d.ts +31 -0
  349. package/dist/via/geo/descriptor.d.ts +54 -0
  350. package/dist/via/geo/geohash.d.ts +95 -0
  351. package/dist/via/geo/index.d.ts +13 -0
  352. package/dist/via/geo/where.d.ts +50 -0
  353. package/dist/via/i18n/core.d.ts +1 -1
  354. package/dist/via/lookup/registry.d.ts +1 -1
  355. package/dist/via/lookup/snapshot.d.ts +3 -3
  356. package/dist/walk-HJ42EISE.js +21 -0
  357. package/dist/walk-HJ42EISE.js.map +1 -0
  358. package/dist/with-audit/coverage/accounting.d.ts +112 -0
  359. package/dist/with-audit/coverage/active.d.ts +29 -0
  360. package/dist/with-audit/coverage/index.d.ts +61 -0
  361. package/dist/with-audit/coverage/sketch.d.ts +75 -0
  362. package/dist/with-audit/periods/periods.d.ts +55 -7
  363. package/dist/with-audit/periods/reserved-collections.d.ts +3 -0
  364. package/dist/with-audit/periods/vault-facade.d.ts +17 -0
  365. package/dist/with-audit/tiers/index.d.ts +51 -0
  366. package/dist/with-audit/tiers/strategy.d.ts +8 -0
  367. package/dist/with-cargo/floor.d.ts +2 -2
  368. package/dist/with-commit/tx/invariants.d.ts +1 -1
  369. package/dist/with-commit/tx/transaction.d.ts +13 -3
  370. package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +4 -3
  371. package/dist/with-formula/materialized-views/emit-memo.d.ts +40 -0
  372. package/dist/with-formula/materialized-views/executor.d.ts +8 -0
  373. package/dist/with-formula/materialized-views/index.d.ts +1 -1
  374. package/dist/with-formula/materialized-views/registry.d.ts +15 -0
  375. package/dist/with-formula/materialized-views/types.d.ts +106 -14
  376. package/dist/with-formula/materialized-views/window-stage.d.ts +11 -0
  377. package/dist/with-formula/overlay-views/types.d.ts +1 -1
  378. package/dist/with-formula/overlay-views/virtual-collection.d.ts +13 -4
  379. package/dist/with-lookup/embeddings/chunks.d.ts +79 -0
  380. package/dist/with-lookup/embeddings/descriptor.d.ts +21 -0
  381. package/dist/with-lookup/embeddings/index.d.ts +3 -0
  382. package/dist/with-lookup/embeddings/ivf-flat.d.ts +93 -0
  383. package/dist/with-lookup/embeddings/vector-index.d.ts +73 -0
  384. package/dist/with-lookup/embeddings/vector-set.d.ts +94 -1
  385. package/dist/with-lookup/embeddings/with-vector-index.d.ts +67 -0
  386. package/dist/with-lookup/indexing/collection-facade.d.ts +85 -2
  387. package/dist/with-lookup/indexing/compound-indexes.d.ts +129 -0
  388. package/dist/with-lookup/indexing/eager-indexes.d.ts +170 -1
  389. package/dist/with-lookup/indexing/index-snapshot.d.ts +62 -0
  390. package/dist/with-lookup/indexing/lazy-builder.d.ts +33 -0
  391. package/dist/with-lookup/indexing/persisted-field-indexes.d.ts +104 -0
  392. package/dist/with-lookup/indexing/persisted-indexes.d.ts +12 -0
  393. package/dist/with-lookup/indexing/sorted-indexes.d.ts +117 -0
  394. package/dist/with-lookup/indexing/unique-constraints.d.ts +106 -23
  395. package/dist/with-lookup/reduce/active.d.ts +2 -24
  396. package/dist/with-lookup/reduce/group-core.d.ts +43 -0
  397. package/dist/with-lookup/reduce/groupby.d.ts +195 -36
  398. package/dist/with-lookup/reduce/incremental-group.d.ts +153 -0
  399. package/dist/with-lookup/reduce/index.d.ts +26 -5
  400. package/dist/with-lookup/reduce/reducers.d.ts +255 -1
  401. package/dist/with-lookup/reduce/reduction.d.ts +44 -13
  402. package/dist/with-lookup/reduce/strategy.d.ts +43 -3
  403. package/dist/with-lookup/reduce/window.d.ts +262 -0
  404. package/dist/with-lookup/search/collection-facade.d.ts +44 -8
  405. package/dist/with-lookup/search/index-store.d.ts +5 -3
  406. package/dist/with-lookup/search/index.d.ts +4 -2
  407. package/dist/with-lookup/search/inverted-index.d.ts +38 -2
  408. package/dist/with-lookup/search/persisted-index-store.d.ts +5 -2
  409. package/dist/with-lookup/search/phrase.d.ts +28 -0
  410. package/dist/with-lookup/search/retrieve-types.d.ts +57 -1
  411. package/dist/with-lookup/search/serialize.d.ts +29 -3
  412. package/dist/with-lookup/search/strategy.d.ts +2 -6
  413. package/dist/with-party/directory/cover/index.d.ts +1 -1
  414. package/dist/with-party/directory/cover/storage.d.ts +1 -1
  415. package/dist/with-party/directory/cover/types.d.ts +1 -1
  416. package/dist/with-party/directory/index.d.ts +1 -1
  417. package/dist/with-party/directory/types.d.ts +1 -1
  418. package/dist/with-party/directory/user-envelope/api.d.ts +1 -1
  419. package/dist/with-party/policy/engine.d.ts +1 -1
  420. package/dist/with-party/policy/index.d.ts +1 -1
  421. package/dist/with-party/policy/presets.d.ts +1 -1
  422. package/dist/with-party/team/authenticators.d.ts +91 -1
  423. package/dist/with-party/team/index.d.ts +3 -2
  424. package/dist/with-party/team/keyring.d.ts +13 -6
  425. package/dist/with-party/team/managed-secret.d.ts +1 -1
  426. package/dist/with-party/team/noydb-facade.d.ts +12 -3
  427. package/dist/with-party/team/wrapped-deks.d.ts +4 -4
  428. package/dist/with-pod/format.d.ts +1 -1
  429. package/dist/with-pod/pod.d.ts +5 -5
  430. package/dist/with-shape/blobs/blob-compaction.d.ts +16 -0
  431. package/dist/with-shape/blobs/blob-set.d.ts +21 -2
  432. package/dist/with-shape/blobs/legacy-sweep.d.ts +35 -0
  433. package/dist/with-shape/introspection/describe.d.ts +6 -0
  434. package/dist/with-shape/introspection/field-meta.d.ts +18 -0
  435. package/dist/with-shape/introspection/types.d.ts +57 -1
  436. package/dist/with-shape/links/vault-facade.d.ts +1 -1
  437. package/dist/with-store/partitioned/index.d.ts +337 -0
  438. package/dist/with-sync/change-broadcast.d.ts +153 -0
  439. package/dist/withdraw-accessible-5NZUPAVA.js +27 -0
  440. package/dist/withdraw-accessible-5NZUPAVA.js.map +1 -0
  441. package/package.json +37 -10
  442. package/dist/api-TXFOVOXX.js +0 -19
  443. package/dist/backup-IJTB4S4M.js.map +0 -1
  444. package/dist/chunk-2577DKXB.js.map +0 -1
  445. package/dist/chunk-2C3AQB46.js.map +0 -1
  446. package/dist/chunk-4GVM6QZA.js.map +0 -1
  447. package/dist/chunk-5WDXHNSW.js.map +0 -1
  448. package/dist/chunk-6GOE5PGN.js.map +0 -1
  449. package/dist/chunk-6X6SEMEE.js.map +0 -1
  450. package/dist/chunk-77K4PG6P.js.map +0 -1
  451. package/dist/chunk-7MG2LHO5.js +0 -1616
  452. package/dist/chunk-7MG2LHO5.js.map +0 -1
  453. package/dist/chunk-7MG7SJT7.js.map +0 -1
  454. package/dist/chunk-7O3Y4PPD.js.map +0 -1
  455. package/dist/chunk-AO42KJO6.js.map +0 -1
  456. package/dist/chunk-ATL3ZVXT.js +0 -151
  457. package/dist/chunk-ATL3ZVXT.js.map +0 -1
  458. package/dist/chunk-BLN54W3L.js.map +0 -1
  459. package/dist/chunk-BM5TWITZ.js.map +0 -1
  460. package/dist/chunk-CUZLNGD5.js.map +0 -1
  461. package/dist/chunk-EQEYOJBM.js.map +0 -1
  462. package/dist/chunk-G56KBWFY.js.map +0 -1
  463. package/dist/chunk-I3ISCYT7.js +0 -159
  464. package/dist/chunk-I3ISCYT7.js.map +0 -1
  465. package/dist/chunk-KG7TZKZG.js.map +0 -1
  466. package/dist/chunk-KJIMGIWO.js.map +0 -1
  467. package/dist/chunk-KTFRCC5C.js.map +0 -1
  468. package/dist/chunk-KUX3OB6D.js.map +0 -1
  469. package/dist/chunk-MUM7IANK.js.map +0 -1
  470. package/dist/chunk-NA2NIJZI.js.map +0 -1
  471. package/dist/chunk-NSITFOCJ.js +0 -99
  472. package/dist/chunk-NSITFOCJ.js.map +0 -1
  473. package/dist/chunk-PT43Y56J.js.map +0 -1
  474. package/dist/chunk-QIBVIOXO.js.map +0 -1
  475. package/dist/chunk-RRNRNANG.js.map +0 -1
  476. package/dist/chunk-RTBJ5IOO.js +0 -87
  477. package/dist/chunk-RTBJ5IOO.js.map +0 -1
  478. package/dist/chunk-SKGMN35F.js.map +0 -1
  479. package/dist/chunk-SL3JAAKN.js.map +0 -1
  480. package/dist/chunk-SPXXX5E4.js.map +0 -1
  481. package/dist/chunk-SPYWXKJN.js.map +0 -1
  482. package/dist/chunk-U4FOQDY4.js.map +0 -1
  483. package/dist/chunk-UXVWEX5L.js.map +0 -1
  484. package/dist/chunk-UXWB3FPF.js.map +0 -1
  485. package/dist/chunk-YMUFFJCO.js.map +0 -1
  486. package/dist/chunk-ZN4FD4PE.js +0 -364
  487. package/dist/chunk-ZN4FD4PE.js.map +0 -1
  488. package/dist/chunk-ZORTV6NH.js.map +0 -1
  489. package/dist/collection-facade-4GRREZ6C.js +0 -45
  490. package/dist/derive-6O5SWQKN.js +0 -21
  491. package/dist/executor-5NIMLSKZ.js +0 -8
  492. package/dist/executor-AQVXV5GZ.js +0 -8
  493. package/dist/executor-PUAO6AKJ.js +0 -29
  494. package/dist/export-accessible-72EQNBMR.js +0 -22
  495. package/dist/extract-partition-PUYNU3UF.js +0 -37
  496. package/dist/find-2ABFCZX4.js +0 -10
  497. package/dist/issue-SIDORTSZ.js +0 -18
  498. package/dist/json-schema-V3M5MUCM.js.map +0 -1
  499. package/dist/kernel/query/join.d.ts +0 -232
  500. package/dist/liberate-LCDPNAG2.js +0 -24
  501. package/dist/link-set-FKNWAYKS.js +0 -30
  502. package/dist/noydb-DUXZPKSN.js +0 -90
  503. package/dist/register-I2DZXGAR.js +0 -31
  504. package/dist/registry-23WF3TGH.js +0 -8
  505. package/dist/registry-4AYVUNXK.js +0 -8
  506. package/dist/registry-VSSQPIGF.js +0 -18
  507. package/dist/registry-YLEWMGAC.js +0 -32
  508. package/dist/request-withdrawal-KFQHEZ25.js +0 -30
  509. package/dist/revoke-DQDZAZZM.js +0 -23
  510. package/dist/signer-WAMDZHEN.js +0 -24
  511. package/dist/storage-FLR73HHF.js +0 -22
  512. package/dist/walk-3UQBRMDV.js +0 -21
  513. package/dist/withdraw-accessible-EL5GXZBS.js +0 -27
  514. /package/dist/{api-TXFOVOXX.js.map → api-GVYEKSJE.js.map} +0 -0
  515. /package/dist/{chunk-BUE2D7PX.js.map → chunk-32YV3TJT.js.map} +0 -0
  516. /package/dist/{chunk-3RCJJBNR.js.map → chunk-3ELRMVJK.js.map} +0 -0
  517. /package/dist/{chunk-6GMZ7HEE.js.map → chunk-3F7GLNAE.js.map} +0 -0
  518. /package/dist/{chunk-2Y4IMKA6.js.map → chunk-3H5KLDZN.js.map} +0 -0
  519. /package/dist/{chunk-VTFVLCUH.js.map → chunk-3JWHAR6E.js.map} +0 -0
  520. /package/dist/{chunk-JBDFMYKQ.js.map → chunk-4AF3TUMP.js.map} +0 -0
  521. /package/dist/{chunk-J6FXAEUG.js.map → chunk-6B3VNR4P.js.map} +0 -0
  522. /package/dist/{chunk-TUFM4UZP.js.map → chunk-6K7DPM7D.js.map} +0 -0
  523. /package/dist/{chunk-K6LHRFBL.js.map → chunk-6LMQ5R26.js.map} +0 -0
  524. /package/dist/{chunk-XLMEZIAJ.js.map → chunk-6OWXYZK2.js.map} +0 -0
  525. /package/dist/{chunk-BOWNCWZT.js.map → chunk-6QSQQLSE.js.map} +0 -0
  526. /package/dist/{chunk-NQKIUNW5.js.map → chunk-A6IJU4TK.js.map} +0 -0
  527. /package/dist/{chunk-QIZPZMUI.js.map → chunk-AOWGXU6D.js.map} +0 -0
  528. /package/dist/{chunk-SFAWCTEM.js.map → chunk-B57AFBY2.js.map} +0 -0
  529. /package/dist/{chunk-GPEQUXHL.js.map → chunk-B6SJIBQK.js.map} +0 -0
  530. /package/dist/{chunk-Q2PU57HC.js.map → chunk-BK2HHQGN.js.map} +0 -0
  531. /package/dist/{collection-facade-4GRREZ6C.js.map → chunk-BKT776R5.js.map} +0 -0
  532. /package/dist/{chunk-FXBIP2WD.js.map → chunk-BUDAGWL5.js.map} +0 -0
  533. /package/dist/{chunk-BOET4NA4.js.map → chunk-CKNGS5MH.js.map} +0 -0
  534. /package/dist/{chunk-YSRUYAVU.js.map → chunk-DE4DH2RZ.js.map} +0 -0
  535. /package/dist/{chunk-6I2RQCET.js.map → chunk-DF73H4UN.js.map} +0 -0
  536. /package/dist/{chunk-K7NTTA6P.js.map → chunk-EOVMT6TB.js.map} +0 -0
  537. /package/dist/{chunk-XTXOCYOJ.js.map → chunk-FCKWQE2H.js.map} +0 -0
  538. /package/dist/{chunk-4OZNXTO4.js.map → chunk-FN5EBVTQ.js.map} +0 -0
  539. /package/dist/{chunk-NKHRDYOR.js.map → chunk-FQ5VRHIB.js.map} +0 -0
  540. /package/dist/{chunk-FAHMLERK.js.map → chunk-FW22ZCXT.js.map} +0 -0
  541. /package/dist/{chunk-RMS3NEA3.js.map → chunk-HT2AHGQI.js.map} +0 -0
  542. /package/dist/{chunk-CHJBLCOG.js.map → chunk-HVSCEIYL.js.map} +0 -0
  543. /package/dist/{chunk-GY4WYNNG.js.map → chunk-ID6Z6E66.js.map} +0 -0
  544. /package/dist/{chunk-EYBQEIUY.js.map → chunk-IEX7NK2K.js.map} +0 -0
  545. /package/dist/{chunk-KUJDV5QH.js.map → chunk-ISZQJBML.js.map} +0 -0
  546. /package/dist/{chunk-LWBCUYZW.js.map → chunk-IT22PWKV.js.map} +0 -0
  547. /package/dist/{chunk-7V4TP3AF.js.map → chunk-J6UOHJY5.js.map} +0 -0
  548. /package/dist/{chunk-JMGDHKQF.js.map → chunk-JCN44NCY.js.map} +0 -0
  549. /package/dist/{chunk-CARABLSQ.js.map → chunk-K6W7BVZ6.js.map} +0 -0
  550. /package/dist/{chunk-453F654R.js.map → chunk-L7DOS3TE.js.map} +0 -0
  551. /package/dist/{chunk-WEQEYLCS.js.map → chunk-LGRLME4M.js.map} +0 -0
  552. /package/dist/{chunk-RMF67KCG.js.map → chunk-LQ4ZHOKP.js.map} +0 -0
  553. /package/dist/{chunk-ZVO4QVPD.js.map → chunk-M522UYZ2.js.map} +0 -0
  554. /package/dist/{chunk-2N4GY74U.js.map → chunk-MTL7MXGR.js.map} +0 -0
  555. /package/dist/{chunk-TFR7DSOV.js.map → chunk-MVZ2TPVP.js.map} +0 -0
  556. /package/dist/{chunk-CPWQB3N5.js.map → chunk-N5DNADLQ.js.map} +0 -0
  557. /package/dist/{chunk-ELNXJMGB.js.map → chunk-NNARWO72.js.map} +0 -0
  558. /package/dist/{chunk-6YXCPL4T.js.map → chunk-PDCG6PR5.js.map} +0 -0
  559. /package/dist/{chunk-REDOOY2Q.js.map → chunk-PSIV7CJV.js.map} +0 -0
  560. /package/dist/{chunk-ANATMFIO.js.map → chunk-QWFHIOD2.js.map} +0 -0
  561. /package/dist/{chunk-X2DLENDQ.js.map → chunk-QWGU66MP.js.map} +0 -0
  562. /package/dist/{chunk-BSFNRLK2.js.map → chunk-S57HTZ6X.js.map} +0 -0
  563. /package/dist/{chunk-SZJ3DLZA.js.map → chunk-SEE332EV.js.map} +0 -0
  564. /package/dist/{chunk-YYP7WSEB.js.map → chunk-SI3V7D4G.js.map} +0 -0
  565. /package/dist/{chunk-UGTBXF5U.js.map → chunk-SOIQ4MM4.js.map} +0 -0
  566. /package/dist/{chunk-VXGSWL6L.js.map → chunk-SYVZW77S.js.map} +0 -0
  567. /package/dist/{chunk-MVMXZYXT.js.map → chunk-TKUOEU2F.js.map} +0 -0
  568. /package/dist/{chunk-SMH6SRX7.js.map → chunk-TROR3ALR.js.map} +0 -0
  569. /package/dist/{chunk-DGOZ4UZD.js.map → chunk-VNNRUGMM.js.map} +0 -0
  570. /package/dist/{chunk-HDOU6I2Y.js.map → chunk-VNPEWFPD.js.map} +0 -0
  571. /package/dist/{chunk-L7CIOOBW.js.map → chunk-WNJ4AVGE.js.map} +0 -0
  572. /package/dist/{chunk-FMZY5Q6L.js.map → chunk-WX5ADGTZ.js.map} +0 -0
  573. /package/dist/{chunk-Q7HZX3IF.js.map → chunk-X4773RBR.js.map} +0 -0
  574. /package/dist/{chunk-ANSIZYEG.js.map → chunk-YNIUIERI.js.map} +0 -0
  575. /package/dist/{chunk-BZ3534YM.js.map → chunk-ZUVBC32X.js.map} +0 -0
  576. /package/dist/{chunk-L742JPKZ.js.map → chunk-ZXABPVM6.js.map} +0 -0
  577. /package/dist/{classified-marker-LI2SF2E4.js.map → classified-marker-W3USLL5M.js.map} +0 -0
  578. /package/dist/{computed-VPOKQK4N.js.map → collection-facade-QU6IJBN3.js.map} +0 -0
  579. /package/dist/{derive-6O5SWQKN.js.map → computed-TPFBX7U4.js.map} +0 -0
  580. /package/dist/{dead-filter-TSWZQRR5.js.map → dead-filter-U2UYKSZL.js.map} +0 -0
  581. /package/dist/{delegation-OVIFFPG4.js.map → delegation-S3AEPW2T.js.map} +0 -0
  582. /package/dist/{enclave-KJOTOH7X.js.map → derive-OX7F6TGG.js.map} +0 -0
  583. /package/dist/{dispatch-WC6X2M6C.js.map → dispatch-3CQDJV5Q.js.map} +0 -0
  584. /package/dist/{dispatch-S73TK3IY.js.map → dispatch-RH4LHKBH.js.map} +0 -0
  585. /package/dist/{executor-5NIMLSKZ.js.map → enclave-27SMF6GF.js.map} +0 -0
  586. /package/dist/{executor-AQVXV5GZ.js.map → executor-B6SRO2QS.js.map} +0 -0
  587. /package/dist/{executor-PUAO6AKJ.js.map → executor-BI5CHWRY.js.map} +0 -0
  588. /package/dist/{export-accessible-72EQNBMR.js.map → executor-RBKM5W5K.js.map} +0 -0
  589. /package/dist/{extract-partition-PUYNU3UF.js.map → export-accessible-VMA3YM5V.js.map} +0 -0
  590. /package/dist/{find-2ABFCZX4.js.map → extract-partition-NF7PXGPP.js.map} +0 -0
  591. /package/dist/{fanout-sidecar-WUFXNYP4.js.map → fanout-sidecar-OT6RYLOL.js.map} +0 -0
  592. /package/dist/{issue-SIDORTSZ.js.map → find-LZMP26KF.js.map} +0 -0
  593. /package/dist/{ledger-F33ZVMTK.js.map → issue-IHARXWW5.js.map} +0 -0
  594. /package/dist/{liberate-LCDPNAG2.js.map → ledger-KZPCMVZY.js.map} +0 -0
  595. /package/dist/{link-set-FKNWAYKS.js.map → liberate-GRLCZMW4.js.map} +0 -0
  596. /package/dist/{managed-secret-PQYKSR6T.js.map → link-set-JCMJ7ULP.js.map} +0 -0
  597. /package/dist/{noydb-DUXZPKSN.js.map → managed-secret-S3MQ6SH6.js.map} +0 -0
  598. /package/dist/{migrate-cek-5ENH64AA.js.map → migrate-cek-V7GKJZ7O.js.map} +0 -0
  599. /package/dist/{register-I2DZXGAR.js.map → noydb-SWQJ3MZS.js.map} +0 -0
  600. /package/dist/{pod-handle-Q736KYON.js.map → pod-handle-WGDFB4C2.js.map} +0 -0
  601. /package/dist/{post-register-LNE2FE2A.js.map → post-register-SLSXIPBV.js.map} +0 -0
  602. /package/dist/{registry-23WF3TGH.js.map → query/all/index.js.map} +0 -0
  603. /package/dist/{registry-4AYVUNXK.js.map → register-F3OYE7MJ.js.map} +0 -0
  604. /package/dist/{registry-VSSQPIGF.js.map → registry-CILQFXFK.js.map} +0 -0
  605. /package/dist/{registry-YLEWMGAC.js.map → registry-XNNYQVKV.js.map} +0 -0
  606. /package/dist/{request-withdrawal-KFQHEZ25.js.map → registry-YCDZV3VW.js.map} +0 -0
  607. /package/dist/{revoke-DQDZAZZM.js.map → registry-ZGYS33TE.js.map} +0 -0
  608. /package/dist/{signer-WAMDZHEN.js.map → request-withdrawal-Z5NC6TXT.js.map} +0 -0
  609. /package/dist/{reveal-67RDQRIN.js.map → reveal-4BCKPEC2.js.map} +0 -0
  610. /package/dist/{stale-DDL7WQEZ.js.map → revoke-ZL62WC2D.js.map} +0 -0
  611. /package/dist/{seed-G42JYN56.js.map → seed-4JS2RJ4A.js.map} +0 -0
  612. /package/dist/{storage-FLR73HHF.js.map → signer-MBZZMXTC.js.map} +0 -0
  613. /package/dist/{walk-3UQBRMDV.js.map → stale-TJ3DYZMQ.js.map} +0 -0
  614. /package/dist/{withdraw-accessible-EL5GXZBS.js.map → storage-SVMKRGCW.js.map} +0 -0
  615. /package/dist/{store-coordination-provider-MEQ2MJQG.js.map → store-coordination-provider-2O2VUHKW.js.map} +0 -0
  616. /package/dist/{verify-KOV2G5D6.js.map → verify-RVT67IMH.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-party/directory/user-envelope/api.ts"],"sourcesContent":["/**\n * Public `vault.user.*` API surface.\n *\n * Three families:\n * - Write-self: `me` / `updateMe` / `setMe` — always target the writer's\n * own keyringId. **Own-only write rule** is structural — no method\n * exists to write someone else's envelope.\n * - Read-anyone: `get` / `list` — read other principals' envelopes\n * (subject to `view-team-profiles` policy gate).\n * - Reactive: `subscribe` / `live` — in-process event emission on local\n * writes. Cross-instance updates land via the team/sync engine and\n * surface to subscribers when the sync diff replays through this API.\n *\n * @see design-history/2026-05-05-user-envelope-design.md\n *\n * @module\n */\nimport { PolicyDeniedError } from '../../../kernel/errors.js'\nimport {\n loadUserEnvelope,\n saveUserEnvelope,\n listUserEnvelopeIds,\n} from './storage.js'\nimport type {\n UserEnvelope,\n DeepPartialOrNull,\n Unsubscribe,\n UserEnvelopePresented,\n LiveUserEnvelope,\n VaultUserApi,\n UserApiDeps,\n UserApiFactory,\n} from '../../../kernel/types.js'\nimport {\n persistUserVisibility,\n readUserVisibility,\n} from '../visibility.js'\nimport type { UserVisibility } from '../types.js'\nimport type { ExportAccessibleOptions } from '../../../with-audit/portability/export-accessible.js'\nimport type { WithdrawAccessibleOptions, WithdrawResult } from '../../../with-audit/portability/withdraw-accessible.js'\nimport type {\n RequestWithdrawalOptions,\n RequestWithdrawalResult,\n WithdrawalRequest,\n WithdrawalRequestStatus,\n ApproveWithdrawalOptions,\n RejectWithdrawalOptions,\n} from '../../../with-audit/portability/request-withdrawal.js'\n\ninterface ChangeListener<T = unknown> {\n (env: UserEnvelope<T> | null): void\n}\n\n/**\n * Implementation behind `vault.user`. Constructed once per Vault via\n * {@link createUserApi}, holds the writer's keyringId in closure so\n * `updateMe`/`setMe` cannot target any other principal — the own-only\n * rule is enforced at the type level (no `set(otherKeyringId, …)`\n * method) AND at runtime (the keyringId argument simply doesn't exist\n * on the write path).\n */\nexport class UserApi implements VaultUserApi {\n /** keyringId → set of listeners. Wildcard '*' fires on every change. */\n private readonly listeners = new Map<string, Set<ChangeListener>>()\n\n private readonly adapter: UserApiDeps['adapter']\n private readonly vaultName: UserApiDeps['vaultName']\n /** The writer's own keyringId. Frozen at construction time. */\n private readonly writerKeyringId: UserApiDeps['writerKeyringId']\n private readonly getDek: UserApiDeps['getDek']\n /**\n * Policy-gate validator. When omitted, gates are skipped — useful\n * for low-level tests that exercise the storage layer directly.\n * Production paths always wire the Noydb-backed implementation.\n */\n private readonly checkGate?: UserApiDeps['checkGate']\n /**\n * Noydb-backed `exportMyAccessibleData`, injected by the Vault\n * (which holds the keyring + bundle machinery). Omitted in low-level tests.\n */\n private readonly exportAccessible?: UserApiDeps['exportAccessible']\n /**\n * Noydb-backed `unilateralWithdrawal`, injected by the Vault.\n * Destructive — extract + dispose (delete | freeze). Omitted in low-level tests.\n */\n private readonly unilateralWithdraw?: UserApiDeps['unilateralWithdraw']\n /**\n * Noydb-backed two-party withdrawal ceremony, injected by the\n * Vault. requestWithdraw = requester side; the rest = owner side.\n */\n private readonly requestWithdraw?: UserApiDeps['requestWithdraw']\n private readonly listWithdrawals?: UserApiDeps['listWithdrawals']\n private readonly approveWithdraw?: UserApiDeps['approveWithdraw']\n private readonly rejectWithdraw?: UserApiDeps['rejectWithdraw']\n\n constructor(deps: UserApiDeps) {\n this.adapter = deps.adapter\n this.vaultName = deps.vaultName\n this.writerKeyringId = deps.writerKeyringId\n this.getDek = deps.getDek\n this.checkGate = deps.checkGate\n this.exportAccessible = deps.exportAccessible\n this.unilateralWithdraw = deps.unilateralWithdraw\n this.requestWithdraw = deps.requestWithdraw\n this.listWithdrawals = deps.listWithdrawals\n this.approveWithdraw = deps.approveWithdraw\n this.rejectWithdraw = deps.rejectWithdraw\n }\n\n /**\n * File a two-party withdrawal request for the caller's accessible\n * scope. Non-destructive (writes a pending request); an owner later approves\n * or rejects. This is the path for read-only roles (`client`/`viewer`) that\n * cannot self-serve a destructive `unilateralWithdrawal`. Gated by\n * `user-request-withdrawal` (enabled by default).\n */\n async requestWithdrawal(opts: RequestWithdrawalOptions = {}): Promise<RequestWithdrawalResult> {\n if (this.checkGate) await this.checkGate('user-request-withdrawal')\n if (!this.requestWithdraw) {\n throw new Error('requestWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.requestWithdraw(opts)\n }\n\n /** Owner side: list filed withdrawal requests (optionally by status). */\n async listWithdrawalRequests(opts: { status?: WithdrawalRequestStatus } = {}): Promise<WithdrawalRequest[]> {\n if (!this.listWithdrawals) {\n throw new Error('listWithdrawalRequests requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.listWithdrawals(opts)\n }\n\n /**\n * Owner side: approve a pending request. Extracts the requester's\n * recorded scope under firm authority, disposes of the source per the\n * request's disposition, and returns the re-keyed bundle to hand back. Gated\n * by `approve-user-withdrawal` (tier-2 default) + owner/admin role.\n */\n async approveWithdrawal(requestId: string, opts: ApproveWithdrawalOptions = {}): Promise<WithdrawResult> {\n if (this.checkGate) await this.checkGate('approve-user-withdrawal')\n if (!this.approveWithdraw) {\n throw new Error('approveWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.approveWithdraw(requestId, opts)\n }\n\n /** Owner side: reject a pending request (no data is touched). */\n async rejectWithdrawal(requestId: string, opts: RejectWithdrawalOptions = {}): Promise<WithdrawalRequest> {\n if (this.checkGate) await this.checkGate('approve-user-withdrawal')\n if (!this.rejectWithdraw) {\n throw new Error('rejectWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.rejectWithdraw(requestId, opts)\n }\n\n /**\n * Single-party withdrawal: export the caller's accessible scope\n * (re-keyed) and dispose of the source (`delete` or `freeze`). Gated by the\n * fail-closed built-in `client-unilateral-withdraw` policy — undefined or\n * disabled → throws (use `requestWithdrawal`). The firm enables it at vault\n * creation.\n */\n async unilateralWithdrawal(opts: WithdrawAccessibleOptions): Promise<WithdrawResult> {\n if (this.checkGate) await this.checkGate('client-unilateral-withdraw')\n if (!this.unilateralWithdraw) {\n throw new Error('unilateralWithdrawal requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.unilateralWithdraw(opts)\n }\n\n /**\n * Export the calling user's accessible scope as a portable, re-keyed\n * `.noydb` bundle. Non-destructive and **always allowed** (data sovereignty\n * by construction) but audited. Scope = the caller's DEK access set.\n */\n async exportMyAccessibleData(opts: ExportAccessibleOptions = {}): Promise<Uint8Array> {\n if (!this.exportAccessible) {\n throw new Error('exportMyAccessibleData requires a Noydb-backed vault (not a bare UserApi)')\n }\n return this.exportAccessible(opts)\n }\n\n // ─── Write-self ──────────────────────────────────────────────────────\n\n /** Read the writer's own envelope. Returns null if never written. */\n async me<T = unknown>(): Promise<UserEnvelope<T> | null> {\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, this.writerKeyringId, dek)\n }\n\n /**\n * Deep-merge a partial patch into the writer's own envelope. Creates\n * the envelope on first call. Optimistic-concurrency safe — a stale\n * `_v` (parallel writer on another device) throws `ConflictError`.\n *\n * Patch semantics:\n * - `undefined` (or omitted key) — skip; existing value preserved\n * - `null` — delete the field from the merged result\n * - any other value — overwrite (deep-merge for plain objects,\n * replace for primitives / arrays)\n *\n * To clear a field, pass `null` rather than `undefined`. Callers\n * with shape `T = string | null` where `null` is a meaningful value\n * should use `setMe` for that specific field instead — `null` here\n * always means delete.\n *\n * Gated by the `edit-own-profile` policy gate (default `minTier: 3`).\n * Pass `presented` to satisfy tightened policies that require a\n * factor proof (e.g. STRICT_POLICY's TOTP requirement).\n */\n async updateMe<T extends object = Record<string, unknown>>(\n patch: DeepPartialOrNull<T>,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const current = await loadUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n dek,\n )\n const merged: T = current ? deepMerge(current.data, patch) : (patch as unknown as T)\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n merged,\n dek,\n current?._v ?? 0,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n /**\n * Replace the writer's own envelope with `payload`. Use sparingly —\n * `updateMe` is the canonical mutation. No `expectedVersion` check;\n * callers explicitly take last-write-wins semantics.\n *\n * Gated by `edit-own-profile`. See `updateMe` for `presented` usage.\n */\n async setMe<T = unknown>(\n payload: T,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T>> {\n if (this.checkGate) await this.checkGate('edit-own-profile', presented)\n const dek = await this.getDek()\n const written = await saveUserEnvelope<T>(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n payload,\n dek,\n )\n this.fireChange(this.writerKeyringId, written)\n return written\n }\n\n // ─── Visibility ──────────────────────────────────────────────────────\n\n /**\n * Read the current user's visibility flag from\n * `_meta/visibility/<keyringId>`. Returns `{ hidden: false }` when no\n * document has been persisted (the default-visible case).\n */\n async getMyVisibility(): Promise<UserVisibility> {\n const persisted = await readUserVisibility(this.adapter, this.vaultName, this.writerKeyringId)\n return persisted ?? { hidden: false }\n }\n\n /**\n * Update the current user's visibility in the team directory.\n *\n * - `hidden: true` — opt out of the default `listUsersWithEnvelopes`\n * listing. `owner`/`admin` callers can still see the user by passing\n * `{ includeHidden: true }`.\n * - `hidden: false` — opt back in.\n *\n * Own-only by construction: the keyringId argument doesn't exist on\n * this method, so no caller can hide or unhide another principal.\n *\n * Honest caveat: this is a UX flag, not a privacy guarantee. The\n * envelope ciphertext at `_users/<keyringId>` and the keyring file at\n * `_keyring/<userId>` are both still observable to anyone with direct\n * store read access. See `https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/user-envelope.md` →\n * \"Directory visibility\".\n */\n async setMyVisibility(visibility: UserVisibility): Promise<void> {\n await persistUserVisibility(\n this.adapter,\n this.vaultName,\n this.writerKeyringId,\n { hidden: visibility.hidden },\n )\n }\n\n // ─── Read-anyone ─────────────────────────────────────────────────────\n\n /**\n * Read another principal's envelope by their keyringId. Returns null\n * if the principal exists but has no envelope yet, or if the\n * keyringId does not exist at all.\n *\n * Gated by `view-team-profiles` (default `minTier: 2`) — but ONLY for\n * cross-principal reads. Reading your own envelope (`keyringId ===\n * self`) is never gated; that's just `me()` written long-form.\n */\n async get<T = unknown>(\n keyringId: string,\n presented?: UserEnvelopePresented,\n ): Promise<UserEnvelope<T> | null> {\n if (this.checkGate && keyringId !== this.writerKeyringId) {\n await this.checkGate('view-team-profiles', presented)\n }\n const dek = await this.getDek()\n return loadUserEnvelope<T>(this.adapter, this.vaultName, keyringId, dek)\n }\n\n /**\n * Read every persisted envelope in the vault. Order is store-defined.\n *\n * Gated by `view-team-profiles`. Default policy (`minTier: 2`) lets\n * any authenticated session read all envelopes. Two privacy-strict\n * opt-outs:\n *\n * - `view-team-profiles.enabled: false` → list() returns only the\n * caller's own envelope (silent self-fallback, no thrown error).\n * - `view-team-profiles.minTier: 1` + insufficient tier → throws\n * `PolicyDeniedError` with `reason: 'insufficient-tier'`. The\n * caller is expected to elevate, not silently degrade.\n *\n * The asymmetry is deliberate: `enabled: false` is a deliberate\n * design choice (\"nobody sees teammate profiles in this app\");\n * `insufficient-tier` is \"you need to authenticate further\". Different\n * UX prompts for different intents.\n */\n async list<T = unknown>(presented?: UserEnvelopePresented): Promise<UserEnvelope<T>[]> {\n if (this.checkGate) {\n try {\n await this.checkGate('view-team-profiles', presented)\n } catch (err) {\n if (err instanceof PolicyDeniedError && err.reason === 'disabled') {\n // Privacy-strict opt-out: quietly return only self.\n const me = await this.me<T>()\n return me ? [me] : []\n }\n throw err\n }\n }\n const dek = await this.getDek()\n const ids = await listUserEnvelopeIds(this.adapter, this.vaultName)\n const envelopes = await Promise.all(\n ids.map((id) => loadUserEnvelope<T>(this.adapter, this.vaultName, id, dek)),\n )\n return envelopes.filter((e): e is UserEnvelope<T> => e !== null)\n }\n\n // ─── Reactive ────────────────────────────────────────────────────────\n\n /**\n * Listen for changes to a specific keyringId's envelope. The callback\n * fires synchronously after every successful local `updateMe` /\n * `setMe` for that principal.\n *\n * Cross-instance changes (a teammate edits their profile on their\n * device, the sync engine pulls the diff onto this device) will fire\n * subscribers when the sync layer replays the write through this API.\n * In v1, subscribers do NOT fire on raw store changes — wire your sync\n * layer to call back through `vault.user.setMe` / `updateMe` if you\n * need that.\n *\n * Pass keyringId `'*'` to fire on every change in the vault.\n */\n subscribe<T = unknown>(\n keyringId: string,\n cb: (env: UserEnvelope<T> | null) => void,\n ): Unsubscribe {\n let listeners = this.listeners.get(keyringId)\n if (!listeners) {\n listeners = new Set()\n this.listeners.set(keyringId, listeners)\n }\n const wrapped: ChangeListener = cb as ChangeListener\n listeners.add(wrapped)\n return () => {\n listeners?.delete(wrapped)\n if (listeners && listeners.size === 0) {\n this.listeners.delete(keyringId)\n }\n }\n }\n\n /**\n * Reactive handle that caches the current value and re-reads on every\n * change for the given keyringId. Convenient for framework bindings:\n *\n * const live = vault.user.live<UserShape>(vault.userId)\n * live.subscribe(env => render(env?.data))\n *\n * Initial value is `null` until the first `current()` call materializes\n * it via `vault.user.get()`. Call `stop()` when done to release the\n * subscription.\n */\n live<T = unknown>(keyringId: string): LiveUserEnvelope<T> {\n let value: UserEnvelope<T> | null = null\n let primed = false\n const unsubscribe = this.subscribe<T>(keyringId, (env) => {\n value = env\n })\n\n return {\n current(): UserEnvelope<T> | null {\n if (!primed) {\n primed = true\n // First call: kick off a read but return synchronously. The\n // subscriber will be re-fired by the next write or the caller\n // can await `vault.user.get()` directly for an immediate read.\n }\n return value\n },\n subscribe: (cb) => this.subscribe<T>(keyringId, cb),\n stop: unsubscribe,\n }\n }\n\n // ─── Internal: change emission ───────────────────────────────────────\n\n private fireChange<T>(keyringId: string, env: UserEnvelope<T> | null): void {\n const targeted = this.listeners.get(keyringId)\n if (targeted) for (const l of targeted) l(env)\n const wildcard = this.listeners.get('*')\n if (wildcard) for (const l of wildcard) l(env)\n }\n}\n\n/**\n * Build the `vault.user` API implementation from its dependencies. This is\n * the real {@link UserApiFactory} — `createNoydb()` dynamically imports it\n * and stashes it on the `Noydb` instance so `Vault`'s constructor can call\n * it synchronously.\n */\nexport const createUserApi: UserApiFactory = (deps: UserApiDeps): VaultUserApi => new UserApi(deps)\n\n/**\n * Recursive plain-object deep merge with delete intent.\n *\n * Patch semantics:\n * - `undefined` — skip the key; source value preserved\n * - `null` — delete the key from output (lodash `_.merge` /\n * Firestore `FieldValue.delete()` semantics)\n * - plain object — recurse (deep merge)\n * - any other value — replace (arrays are replaced, not concatenated)\n *\n * Safe against the JS quirk where an own property explicitly set to\n * `undefined` is iterated by `Object.entries`. We dispatch on the value\n * BEFORE writing, so `{ k: undefined }` triggers the skip branch rather\n * than overwriting `out[k]` with undefined.\n */\nfunction deepMerge<T>(source: T, patch: DeepPartialOrNull<T>): T {\n if (!isPlainObject(source) || !isPlainObject(patch)) {\n // Top-level non-object replace. `null` patch at the leaf level\n // would have been caught by the parent recursion's branch table;\n // at the top level it means \"set the whole envelope to null,\"\n // which the type system already prevents (T extends object).\n return patch as unknown as T\n }\n const out: Record<string, unknown> = { ...(source as Record<string, unknown>) }\n for (const [key, patchVal] of Object.entries(patch as Record<string, unknown>)) {\n if (patchVal === undefined) {\n // Skip — preserve the source value at this key. Matches the\n // pre-existing behavior so callers who never used `null` see no diff.\n continue\n }\n if (patchVal === null) {\n // Delete intent. `delete` rather than `out[key] = undefined`\n // because JSON.stringify drops undefined fields silently and\n // we want the deletion to be visible to consumers iterating\n // the merged object (e.g. `Object.keys(merged.profile)`).\n delete out[key]\n continue\n }\n const sourceVal = (source as Record<string, unknown>)[key]\n if (isPlainObject(patchVal)) {\n // Recurse for any plain-object patch — including the \"source is\n // missing this key\" case. Without recursing through a synthetic\n // empty source, nested `null` deletions in the patch would land\n // as literal `null` values instead of triggering the delete\n // branch (e.g. `{ app: { signature: null } }` against a missing\n // `app` would emit `{ app: { signature: null } }` instead of\n // `{ app: {} }`).\n const recurseSource = isPlainObject(sourceVal) ? sourceVal : {}\n out[key] = deepMerge(recurseSource, patchVal as DeepPartialOrNull<typeof recurseSource>)\n } else {\n out[key] = patchVal\n }\n }\n return out as T\n}\n\nfunction isPlainObject(x: unknown): x is Record<string, unknown> {\n if (x === null || typeof x !== 'object') return false\n if (Array.isArray(x)) return false\n const proto = Object.getPrototypeOf(x) as object | null\n return proto === Object.prototype || proto === null\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/materialized-views/executor.ts"],"sourcesContent":["import type { Collection } from '../../kernel/collection.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport { MaterializedViewTooLargeError, MaterializedViewConfigError, LocaleNotSpecifiedError, JoinTooLargeError } from '../../kernel/errors.js'\nimport { DEFAULT_JOIN_MAX_ROWS } from '../../kernel/query/join.js'\nimport type { MaterializedFromMeta, MVQueryContext, MaterializedViewSpec, ProjectionJoinLeg } from './types.js'\nimport type { RegisteredMV } from './registry.js'\nimport { wrapDbWithPredicates } from './registry.js'\nimport { groupAndReduce } from '../../with-lookup/reduce/groupby.js'\nimport { canonicalGroupKey } from '../../with-lookup/reduce/canonical-key.js'\nimport { applyI18nLocale, type I18nTextDescriptor } from '../../via/i18n/core.js'\nimport { canonicalizeMoneyFieldsAsDecimal, decodeMoneyFields } from '../../via/money/normalize.js'\nimport { exactMath } from '../../via/money/exact.js'\nimport { putDerivedOutput, type PutDerivedOutputCtx } from '../../kernel/via/dispatch.js'\nimport { isCollectLeg, resolveLegOwner, type RefLookup } from './projection-legs.js' // #1140\n\n/**\n * Accessor shape passed in from the owning Vault. Mirrors v1's\n * `DerivationStaleAccessor` — provides the per-collection resolver\n * and the active TxContext so refresh writes/tombstones register on\n * `_executed` for rollback symmetry.\n */\nexport interface MVExecutorAccessor {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n /**\n * Vault-shaped accessor passed to the MV's `query()` callback at\n * each refresh. Same instance the registry used at registration\n * time; threading through the executor lets the refresh path\n * re-evaluate the closure against the live vault state.\n */\n getQueryContext(): MVQueryContext\n /**\n * #638 Task 5 — ctx for `putDerivedOutput`'s frozen-period skip+audit. #641: the lazy\n * resolve-on-read caller (`stale.ts#resolveStaleMVOnRead`) now supplies one too — built at\n * the `Collection.get()`/`list()` entry point with a `'resolve-on-read'` sentinel id (no\n * real \"reacting write\" for a read-triggered materialize, mirroring `refreshView()`'s\n * `'refreshView'` sentinel). Still declared optional here since `MVExecutorAccessor` is a\n * general shape and a future caller could reasonably omit it.\n */\n dispatchCtx?: PutDerivedOutputCtx\n}\n\nexport interface RefreshResult {\n /** Rows newly written / overwritten. */\n written: number\n /** Rows tombstoned via `_internalDelete` (only when `onEmpty: 'delete'`). */\n deleted: number\n /** Failed row writes (non-strict mode). */\n failed: number\n /** #782/#785 — `outputCollection:id` entries from the tombstone pass whose ownership stamp\n * couldn't be decoded under the collection's default DEK (undecodable, mirroring\n * `invalidateMVAtRest`'s #776 posture) — ownership UNCONFIRMED. Only populated when\n * `onEmpty: 'delete'`. */\n residueUndecodable: string[]\n /** #782/#785 — `outputCollection:id` entries that decoded, stamp-matched this MV, but\n * `_internalDelete` declined (the #718 tier-elevation gate) — ownership CONFIRMED, a real\n * silent survival, surfaced rather than dropped. Only populated when `onEmpty: 'delete'`. */\n residueDeclined: string[]\n}\n\n/** Default cost ceiling — overridable per-MV via `spec.maxRows`. */\nconst DEFAULT_MAX_ROWS = 100_000\n\n/**\n * Materialize a query terminal that may be a `Query<T>` (call\n * `.toArray()`), an `Reduction<R>` (call `.run()` returning a\n * single object — wrap as a one-row array), or a `GroupedReduction<R>`\n * (call `.run()` returning an array of grouped rows). Branches on\n * available terminal at runtime — no type-discrimination at registration.\n */\nasync function materializeQueryResult(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n q: any,\n mvName: string,\n i18nLocale?: string,\n i18nFields?: Record<string, I18nTextDescriptor>,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n if (typeof q?.toArray === 'function') {\n // Query<T> — non-aggregate path. `.toArray()` returns Promise<T[]>.\n return await q.toArray()\n }\n if (typeof q?.run === 'function') {\n // Reduction<R> or GroupedReduction<R>. `.run()` is synchronous\n // and returns either a single object (Reduction) or an array of\n // rows (GroupedReduction). Promise.resolve() normalizes both\n // sync and async (future) variants.\n // Query-form MV grouping: when the MV declares i18nLocale, pass it +\n // i18nFields so a GroupedReduction resolves i18n group keys before\n // bucketing (the Reduction path ignores the extra arg).\n const runOpts = i18nLocale !== undefined ? { locale: i18nLocale, i18nFields } : undefined\n const result: unknown = await Promise.resolve(q.run(runOpts))\n if (Array.isArray(result)) {\n return result as ReadonlyArray<Record<string, unknown>>\n }\n // Single-aggregate result — wrap as one-row array. The consumer's\n // `rowKey()` should return a stable identity (often a literal\n // constant like `'total'`) since there's only one row.\n return [result as Record<string, unknown>]\n }\n throw new Error(\n `MV \"${mvName}\": query() must return a Query<T>, Reduction, or GroupedReduction. ` +\n `Got something without a .toArray() or .run() terminal.`,\n )\n}\n\n/**\n * Materialize a UNION-form MV: read every arm's source\n * collection, apply each arm's `map` to project rows into the unified\n * MV row shape, concatenate the mapped streams, then optionally run\n * `groupBy` + `aggregate` over the result.\n *\n * Modes (driven by `spec.groupBy` / `spec.aggregate`):\n *\n * - No `groupBy` → return the concatenated mapped rows unchanged.\n * - `groupBy` without `aggregate` → dedupe by composite group key,\n * keep the first row seen per key (later arms don't overwrite\n * earlier arms — Map insertion order rules).\n * - `groupBy` + `aggregate` → delegate to the shared `groupAndReduce`\n * pipeline used by `Query.groupBy().aggregate()`.\n *\n * Per-arm `map` is the schema-unification boundary; the strategy's\n * `TRow` type parameter enforces that every arm projects into the\n * same shape at compile time.\n *\n * @internal\n */\nasync function materializeUnionResult<TRow extends Record<string, unknown>>(\n spec: MaterializedViewSpec<TRow>,\n db: MVQueryContext,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n const unified: TRow[] = []\n for (const arm of spec.unionSources!) {\n const coll = db.collection<Record<string, unknown>>(arm.collection)\n // Optional per-arm FK joins: chain `.join(field, { as, ... })` for\n // each declared leg before terminating. The aliased right-side\n // record lands at `sourceRow[leg.as]`, where the arm's `map` reads\n // it. Cast to `any` because the chained join widens the row type but\n // the executor treats every row as `Record<string, unknown>` — same\n // pattern the query-form join path uses.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let q: any = coll.query()\n if (arm.join?.length) {\n for (const leg of arm.join) {\n q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy })\n }\n }\n const sourceRows = q.toArray() as ReadonlyArray<Record<string, unknown>>\n for (const r of sourceRows) {\n const mapped = arm.map(r)\n // null / undefined means \"omit this source row\" — skip without\n // pushing so groupBy/aggregate never see a null entry.\n if (mapped == null) continue\n unified.push(mapped)\n }\n }\n\n return finalizeMappedRows(spec, unified)\n}\n\n/**\n * Shared post-map tail for the UNION and projection (#810) forms:\n * optional `groupBy` (+ `aggregate`) over the mapped-row stream.\n * Extracted verbatim from `materializeUnionResult` so both forms feed\n * the identical grouping pipeline — i18n group-key resolution, the\n * object-group-key guard, dedup-without-aggregate, and the shared\n * `groupAndReduce` delegate.\n *\n * @internal\n */\nfunction finalizeMappedRows<TRow extends Record<string, unknown>>(\n spec: MaterializedViewSpec<TRow>,\n unified: TRow[],\n): ReadonlyArray<Record<string, unknown>> {\n if (!spec.groupBy) return unified\n\n const groupFields: readonly string[] =\n typeof spec.groupBy === 'string' ? [spec.groupBy] : spec.groupBy\n\n // i18n-aware group keys. An `i18nText` group field carries a raw\n // `{ locale: string }` map, an unstable object key. When `i18nLocale` is\n // declared (with `i18nFields` describing those fields), resolve the declared\n // group-key i18n fields to it at the `mv` layer FIRST — the same unified-rows\n // boundary where money is threaded — so buckets are stable strings and the\n // `mv`-layer `onMissing` policy fires here.\n if (spec.i18nLocale !== undefined && spec.i18nFields !== undefined) {\n const groupI18n: Record<string, I18nTextDescriptor> = {}\n for (const f of groupFields) {\n const d = spec.i18nFields[f]\n if (d !== undefined) groupI18n[f] = d\n }\n if (Object.keys(groupI18n).length > 0) {\n for (let i = 0; i < unified.length; i++) {\n unified[i] = applyI18nLocale(unified[i] as Record<string, unknown>, groupI18n, spec.i18nLocale, undefined, 'mv') as TRow\n }\n }\n }\n // Guard (always): a remaining object-valued group key — an undeclared i18n\n // field or a locale-less MV — would bucket on a map. Refuse, don't bucket wrong.\n for (const f of groupFields) {\n for (const row of unified) {\n const v = (row as Record<string, unknown>)[f]\n if (v !== null && typeof v === 'object') {\n throw new LocaleNotSpecifiedError(\n f,\n `Materialized view \"${spec.name}\" groups by \"${f}\", whose value is a raw i18n locale map — ` +\n `an unstable object group key. Declare { i18nLocale, i18nFields } on the MV to resolve it at ` +\n `the 'mv' layer, or group by a dictKey/staticDict code (the stable key) and resolve the label at read time.`,\n )\n }\n }\n }\n\n // groupBy without aggregate — dedupe by composite key, keep first\n // seen row per key. Useful for cross-arm uniqueness (e.g. unify two\n // sibling collections, keeping one row per natural key).\n if (!spec.aggregate) {\n const seen = new Map<string, TRow>()\n for (const row of unified) {\n const k = canonicalGroupKey(groupFields, row as Record<string, unknown>)\n if (!seen.has(k)) seen.set(k, row)\n }\n return [...seen.values()]\n }\n\n // groupBy + aggregate — delegate to the shared pipeline used by\n // `Query.groupBy().aggregate()`. Result rows carry each grouped\n // field in declaration order followed by the spec's reducer outputs.\n return groupAndReduce<Record<string, unknown>>(unified, groupFields, spec.aggregate, spec.moneyFields)\n}\n\n/**\n * Apply the spec's `derive` to each finished row (#1007).\n *\n * Three rules, all of them about keeping the hook narrow enough that the\n * engine never has to reason about the function:\n *\n * - a `null` / `undefined` return leaves the row untouched;\n * - a returned key that collides with a `groupBy` field is refused — group\n * keys are the row's identity and feed `rowKey`, so letting `derive`\n * rewrite one would silently re-home the row into a different bucket than\n * the one it was aggregated for;\n * - a derived field declared in `moneyFields` is quantised through its\n * descriptor, so the stored value is exact at the declared scale instead of\n * whatever the user's arithmetic produced.\n *\n * @internal\n */\nfunction applyDerive<TRow extends Record<string, unknown>>(\n spec: MaterializedViewSpec<TRow>,\n rows: ReadonlyArray<Record<string, unknown>>,\n): ReadonlyArray<Record<string, unknown>> {\n const groupFields = new Set<string>(\n spec.groupBy === undefined ? [] : typeof spec.groupBy === 'string' ? [spec.groupBy] : spec.groupBy,\n )\n return rows.map((row) => {\n // `derive` sees the row the way a READER would: money decoded to its\n // canonical decimal form, not the scaled integer the reducer left behind.\n // Handing over `1005` where the schema says `10.05` would make every\n // derived money expression quietly wrong by a factor of the scale.\n // `'raw'` keeps it at the exact decimal rather than a locale-formatted\n // string, which is what `exact.*` consumes.\n const view = spec.moneyFields ? decodeMoneyFields(row, spec.moneyFields, 'raw') : row\n const patch = spec.derive!(view as TRow, exactMath)\n if (patch === null || patch === undefined) return row\n for (const key of Object.keys(patch)) {\n if (groupFields.has(key)) {\n throw new MaterializedViewConfigError(\n `Materialized view \"${spec.name}\": derive() returned the group key \"${key}\". ` +\n 'A group key is the row\\'s identity and feeds rowKey — rewriting it would re-home the ' +\n 'row into a bucket it was not aggregated for. Emit a differently-named field instead.',\n )\n }\n }\n // Canonicalize the PATCH only, into the DECIMAL form the money-aware\n // reducers emit — not the scaled-integer storage form a collection uses.\n // An MV row's money fields are decimal strings, so a derived field\n // quantised into storage form reads back as the scaled integer beside\n // correctly-decoded siblings: `\"1000000\"` next to `\"10000.00\"` (#1018).\n // Precision is still enforced; only the output shape differs.\n const canonical = spec.moneyFields\n ? canonicalizeMoneyFieldsAsDecimal(patch, spec.moneyFields)\n : patch\n return { ...row, ...canonical }\n })\n}\n\n/**\n * Materialize a projection-form MV (#810): hydrate the primary source\n * rows, resolve forward FK legs through the same `.join()` machinery\n * the UNION arms use, attach reverse \"collect\" legs via one\n * hash-grouped snapshot pass per leg, then run the projection `map`\n * (null / undefined omits the primary row) and the shared post-map\n * grouping tail.\n *\n * @internal\n */\nasync function materializeProjectionResult<TRow extends Record<string, unknown>>(\n spec: MaterializedViewSpec<TRow>,\n db: MVQueryContext,\n): Promise<ReadonlyArray<Record<string, unknown>>> {\n const projection = spec.projection!\n const coll = db.collection<Record<string, unknown>>(projection.source)\n // ROOT forward legs chain through the query builder exactly like UNION arm\n // joins — ref() resolution, dangling-mode semantics, presentation dressing,\n // and ceilings all ride the existing `.join()` path. Cast to `any` for the\n // same reason `materializeUnionResult` does.\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let q: any = coll.query()\n for (const leg of projection.joins) {\n if (isCollectLeg(leg) || leg.from !== undefined) continue\n q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy })\n }\n let rows = q.toArray() as Array<Record<string, unknown>>\n // Remaining legs in DECLARATION order — which is what makes `from` work: a leg\n // may only name an alias declared earlier, so by the time it runs, the rows it\n // attaches to are already on the row. Root collect legs are in this loop too;\n // they were never part of the query chain.\n for (const leg of projection.joins) {\n if (isCollectLeg(leg)) {\n rows = applyCollectLeg(rows, leg, spec.name, projection.source, projection.joins, db)\n } else if (leg.from !== undefined) {\n rows = applyLegRelativeForwardLeg(rows, leg, spec.name, projection.source, projection.joins, db)\n }\n }\n const mapped: TRow[] = []\n for (const r of rows) {\n const m = projection.map(r)\n // null / undefined means \"omit this primary row\" — same contract\n // as the UNION arm `map`.\n if (m == null) continue\n mapped.push(m)\n }\n return finalizeMappedRows(spec, mapped)\n}\n\n/**\n * A `RefLookup` backed by the live join contexts — the executor's answer to the\n * same question the registry answers from the ref registry. Reads through, so a\n * ref declared after `openVault()` (the only place refs CAN be declared) is\n * visible here.\n *\n * @internal\n */\nfunction legOwnerLookup(db: MVQueryContext): RefLookup {\n return (collection, field) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const q = db.collection<Record<string, unknown>>(collection).query() as any\n return (q._joinContext?.()?.resolveRef(field) as { target: string } | null | undefined) ?? null\n }\n}\n\n/**\n * Attach one FORWARD leg that hangs off another leg's alias (#1140).\n *\n * Root forward legs ride `Query.join()` and are resolved before this runs; a\n * leg-relative one cannot, because the FK it follows lives on an already-attached\n * record rather than on a column of the primary query. It is therefore resolved\n * the same way a collect leg is — one snapshot of the target collection, indexed\n * by id — which keeps it O(N+M) rather than one `get()` per row.\n *\n * Attaches `null` when the `from` alias is null (a dangling ref upstream), when\n * the FK is nullish, or when the target row is missing. That is deliberately\n * outer-by-nature and matches the collect leg's empty array: a projection MV\n * must not drop a primary row because something two hops away is absent.\n *\n * @internal\n */\nfunction applyLegRelativeForwardLeg(\n primaryRows: ReadonlyArray<Record<string, unknown>>,\n leg: Extract<ProjectionJoinLeg, { field: string }>,\n mvName: string,\n source: string,\n joins: ReadonlyArray<ProjectionJoinLeg>,\n db: MVQueryContext,\n): Array<Record<string, unknown>> {\n const lookup = legOwnerLookup(db)\n const owner = resolveLegOwner(leg, joins, source, lookup)\n const refDesc = owner === null ? null : lookup(owner, leg.field)\n if (owner === null || refDesc === null) {\n throw new MaterializedViewConfigError(\n `\"${mvName}\": projection leg \"${leg.as}\" requires a ref() on field \"${leg.field}\" of `\n + `collection \"${owner ?? `(behind from: \"${leg.from}\")`}\" — declare it on that collection, `\n + `then retry`,\n )\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const targetQ = db.collection<Record<string, unknown>>(refDesc.target).query() as any\n const byId = new Map<string, Record<string, unknown>>()\n for (const row of targetQ.toArray() as Array<Record<string, unknown>>) {\n const id = coerceCollectKey(row.id)\n if (id !== null) byId.set(id, row)\n }\n return primaryRows.map((row) => {\n const anchor = leg.from === undefined\n ? row\n : (row[leg.from] as Record<string, unknown> | null | undefined)\n const key = anchor == null ? null : coerceCollectKey(anchor[leg.field])\n return { ...row, [leg.as]: key === null ? null : byId.get(key) ?? null }\n })\n}\n\n/**\n * Attach one reverse \"collect\" leg (#810): every row of `leg.collect`\n * whose `leg.on` field references the ATTACH POINT's id lands in a\n * possibly-empty ARRAY under `leg.as` on that primary row. One\n * snapshot pass over the collect collection, hash-grouped by the `on`\n * FK — O(N+M), mirroring the forward hash-join fallback.\n *\n * The attach point is the primary record, or — with `from` (#1140) — the record\n * already attached under that alias. Matching then keys off `row[from].id`\n * instead of `row.id`, and `leg.on` must ref THAT leg's collection. A row whose\n * `from` alias is null (a dangling forward ref) gets `[]`, which is the same\n * outer-by-nature semantics a root collect leg already has for a primary record\n * with no children.\n *\n * Semantic check (first materialization, not factory time — parity\n * with join-time ref errors): `leg.on` must carry a `ref()` declared\n * on the collect collection targeting the attach point's collection.\n *\n * @internal\n */\nfunction applyCollectLeg(\n primaryRows: ReadonlyArray<Record<string, unknown>>,\n leg: { readonly collect: string; readonly on: string; readonly as: string; readonly from?: string; readonly maxRows?: number },\n mvName: string,\n source: string,\n joins: ReadonlyArray<ProjectionJoinLeg>,\n db: MVQueryContext,\n): Array<Record<string, unknown>> {\n const childColl = db.collection<Record<string, unknown>>(leg.collect)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const childQ = childColl.query() as any\n const refDesc = childQ._joinContext?.()?.resolveRef(leg.on) as { target: string } | null | undefined\n // The collection this leg matches AGAINST: the projection source for a root\n // leg, the `from` leg's own target otherwise (#1140).\n const expected = resolveLegOwner(leg, joins, source, legOwnerLookup(db))\n const expectedName = expected ?? source\n if (refDesc == null) {\n throw new MaterializedViewConfigError(\n `\"${mvName}\": projection collect leg \"${leg.as}\" requires a ref() on field \"${leg.on}\" of `\n + `collection \"${leg.collect}\" targeting \"${expectedName}\" — declare `\n + `refs: { ${leg.on}: ref('${expectedName}') } on collection \"${leg.collect}\", then retry`,\n )\n }\n if (refDesc.target !== expectedName) {\n throw new MaterializedViewConfigError(\n `\"${mvName}\": projection collect leg \"${leg.as}\" expects field \"${leg.on}\" of collection `\n + `\"${leg.collect}\" to reference ${leg.from === undefined\n ? `the projection source \"${expectedName}\"`\n : `\"${expectedName}\" (the collection behind from: \"${leg.from}\")`}`\n + `, but its ref() targets \"${refDesc.target}\"`,\n )\n }\n const maxRows = leg.maxRows ?? DEFAULT_JOIN_MAX_ROWS\n const groups = new Map<string, Array<Record<string, unknown>>>()\n for (const child of childQ.toArray() as Array<Record<string, unknown>>) {\n const key = coerceCollectKey(child[leg.on])\n // Nullish / non-scalar FK values mean \"no reference\" — same\n // narrowing as the forward join path's key coercion.\n if (key === null) continue\n const bucket = groups.get(key)\n if (bucket) bucket.push(child)\n else groups.set(key, [child])\n }\n const out: Array<Record<string, unknown>> = []\n for (const row of primaryRows) {\n // `from` matches against the attached record's id, not the primary row's.\n const anchor = leg.from === undefined\n ? row\n : (row[leg.from] as Record<string, unknown> | null | undefined)\n const key = anchor == null ? null : coerceCollectKey(anchor.id)\n const children = key === null ? [] : groups.get(key) ?? []\n if (children.length > maxRows) {\n throw new JoinTooLargeError({\n leftRows: primaryRows.length,\n rightRows: children.length,\n maxRows,\n side: 'right',\n message:\n `projection MV \"${mvName}\": collect leg \"${leg.as}\" gathered ${children.length} ` +\n `\"${leg.collect}\" rows for one \"${source}\" record, exceeding the ${maxRows}-row ` +\n `per-primary-row ceiling. Raise the ceiling via { maxRows } on the leg if the ` +\n `fan-out genuinely fits in memory, or restructure the child collection.`,\n })\n }\n out.push({ ...row, [leg.as]: children })\n }\n return out\n}\n\n/**\n * Coerce an unknown FK value into a collect-grouping key. Same\n * narrowing as the join path's `coerceRefKey` (not exported from\n * there): strings and numbers are legitimate ref values; anything\n * else is \"no reference\" and returns `null`.\n *\n * @internal\n */\nfunction coerceCollectKey(value: unknown): string | null {\n if (value === null || value === undefined) return null\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'bigint') return String(value)\n return null\n}\n\n/**\n * Run an MV's `query()` and write the result rows to the output\n * collection. Same-DEK encryption: routes through the standard\n * `Collection.put` pipeline, so the output collection's DEK is what\n * gets used (matches the v2 spec's \"same DEK as the left-most source\"\n * invariant — `Collection.put` looks up the DEK by collection name,\n * and the output collection IS the MV's owned collection).\n *\n * Stamps `_materializedFrom` onto every emitted row.\n *\n * **Tombstoning:** when `spec.onEmpty: 'delete'` (default), rows\n * that existed in a prior refresh but no longer appear in the new\n * materialized result are deleted via `Collection._internalDelete` —\n * the housekeeping bypass primitive prevents user\n * `onDelete` guards on the output collection from firing on these\n * system-internal deletes. `onEmpty: 'keep'` opts out (rows from\n * prior refreshes linger even when the new result lacks them).\n *\n * **Cost ceiling:** if the materialized row count exceeds\n * `spec.maxRows` (default 100k), throws `MaterializedViewTooLargeError`\n * before any writes hit the store — so strict-mode rollback is\n * clean.\n *\n * **Strict mode:** `spec.strict === true` re-throws on any\n * row-write failure; the active TxContext registration means the\n * source-write rolls back atomically via `revertExecuted`.\n *\n * @internal\n */\nexport const MaterializedViewExecutor = {\n async refresh(\n reg: RegisteredMV,\n accessor: MVExecutorAccessor,\n ): Promise<RefreshResult> {\n const spec = reg.spec\n const outputColl = accessor.getCollection(reg.outputCollection)\n const maxRows = spec.maxRows ?? DEFAULT_MAX_ROWS\n const onEmpty = spec.onEmpty ?? 'delete'\n const strict = spec.strict ?? false\n\n // 1. Materialize the query (branches on terminal shape). If the\n // MV declared predicates, wrap the query context the same way\n // the registry did at registration time so `.wherePredicate()`\n // calls resolve to the registered functions.\n const baseCtx = accessor.getQueryContext()\n const ctxForQuery: MVQueryContext = spec.predicates\n ? wrapDbWithPredicates(baseCtx, spec.predicates)\n : baseCtx\n // UNION-form strategies: read every arm, map to the unified\n // row shape, concatenate, then optionally groupBy + aggregate.\n // Projection-form strategies (#810): hydrate the primary source,\n // attach forward + collect legs, map, then the same optional\n // grouping tail. The single-source `query()` path is untouched.\n let rows: ReadonlyArray<Record<string, unknown>>\n if (spec.unionSources) {\n rows = await materializeUnionResult(spec, ctxForQuery)\n } else if (spec.projection) {\n rows = await materializeProjectionResult(spec, ctxForQuery)\n } else {\n const q = spec.query!(ctxForQuery)\n rows = await materializeQueryResult(q, spec.name, spec.i18nLocale, spec.i18nFields)\n }\n\n // #1007 — the post-aggregate projection. Applied here, after every form has\n // produced its finished rows, so union / projection / query all get the\n // same single definition of \"last step before materialisation\".\n if (spec.derive) rows = applyDerive(spec, rows)\n\n // #777 — exclude this MV's OWN previously-stamped output rows from the\n // input scan. A same-collection Query-form MV copies the whole source row\n // verbatim into its output, so a stale output row can itself satisfy the\n // MV's own input filter (when the filter is on a field disjoint from the\n // partition field) and get re-selected as if it were live source — the\n // row self-perpetuates after its true source is gone. Prior output is\n // never this MV's own source.\n rows = rows.filter((row) => {\n const stamp = row._materializedFrom as { mvName?: string } | undefined\n return stamp?.mvName !== spec.name\n })\n\n // 2. Cost ceiling check BEFORE any writes — keeps the rollback\n // clean if the source-write is wrapped in a transaction.\n if (rows.length > maxRows) {\n throw new MaterializedViewTooLargeError(spec.name, rows.length, maxRows)\n }\n\n const txCtx = accessor.getActiveTxContext()\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const adapter = (outputColl as any).adapter as {\n get(v: string, c: string, i: string): Promise<EncryptedEnvelope | null>\n }\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const vaultName = (outputColl as any).vault as string\n\n // 3. Compute the post-refresh id set so we can diff against the\n // prior-emitted id set for tombstoning (when onEmpty === 'delete').\n const newIds = new Set<string>()\n const enrichedRows: Array<{ id: string; record: Record<string, unknown> }> = []\n for (const row of rows) {\n const id = spec.rowKey(row)\n newIds.add(id)\n const meta: MaterializedFromMeta = {\n mvName: spec.name,\n queryHash: reg.queryHash,\n sourceVersions: {},\n materializedAt: new Date().toISOString(),\n }\n enrichedRows.push({ id, record: { ...row, _materializedFrom: meta } })\n }\n\n // 4. Write the new rows.\n let written = 0\n let failed = 0\n for (const { id, record } of enrichedRows) {\n try {\n if (txCtx !== null) {\n const prior = await adapter.get(vaultName, reg.outputCollection, id)\n txCtx._executed.push({\n op: { type: 'put', vaultName, collectionName: reg.outputCollection, id },\n priorEnvelope: prior,\n })\n }\n if (accessor.dispatchCtx) {\n if (await putDerivedOutput(outputColl, id, record, accessor.dispatchCtx) === 'written') written++\n } else {\n await outputColl.put(id, record)\n written++\n }\n } catch (err) {\n failed++\n if (strict) throw err\n \n console.warn(`[mv] \"${spec.name}\" row write failed:`, err)\n }\n }\n\n // 5. Tombstone rows that existed before but don't appear now.\n // `onEmpty: 'keep'` skips this pass entirely. Uses\n // `_internalDelete` so a user-registered `onDelete` on the\n // output collection does NOT fire on housekeeping (composition fix).\n let deleted = 0\n const residueUndecodable: string[] = []\n const residueDeclined: string[] = []\n if (onEmpty === 'delete') {\n const priorIds = await listOutputIds(outputColl)\n for (const priorId of priorIds) {\n if (newIds.has(priorId)) continue\n // #762 — a same-collection partition MV (`output: { collection: <source>, partition }`,\n // the DERIV-PP30-001 shape) writes INTO its own source collection, so `listOutputIds`\n // also returns untouched USER source rows here. Decode each candidate and only\n // tombstone rows THIS MV stamped via `_materializedFrom.mvName` — the exact discipline\n // `invalidateMVAtRest` uses (stale.ts:151-162). An unstamped row, or one stamped by a\n // different MV, is never this MV's to delete.\n const priorEnvelope = await adapter.get(vaultName, reg.outputCollection, priorId)\n if (priorEnvelope === null) continue\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const priorDecoded = await (outputColl as any)._decodeEnvelope(priorEnvelope, priorId)\n if (priorDecoded === null) {\n // #782 part a — ownership unknown (undecodable, e.g. elevated above tier 0 on a\n // tiered output collection): can't rule out this being THIS MV's own stamped row.\n // Surface it rather than silently skip (mirrors invalidateMVAtRest's #776 posture —\n // previously this leg had NO residue channel at all).\n residueUndecodable.push(`${reg.outputCollection}:${priorId}`)\n continue\n }\n const priorStampedBy =\n typeof priorDecoded === 'object'\n ? (priorDecoded as Record<string, unknown>)._materializedFrom as { mvName?: string } | undefined\n : undefined\n if (priorStampedBy?.mvName !== spec.name) continue\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const outAny = outputColl as any\n if (typeof outAny._internalDelete === 'function') {\n // #776 part b — gate on the boolean, matching invalidateMVAtRest's discipline\n // (stale.ts): `_internalDelete` returns `false` for a #718 elevated-skip (no\n // erasure happened), and that must not inflate the tombstone count.\n if (await outAny._internalDelete(priorId, txCtx)) {\n deleted++\n } else {\n // #782 part b — decoded AND stamp-owned, but erasure was declined (#718\n // tier-elevation gate). Ownership IS confirmed here — a real silent survival,\n // not a legit stamp-mismatch skip. Surface it too.\n residueDeclined.push(`${reg.outputCollection}:${priorId}`)\n }\n } else {\n // Defensive fallback — should never hit in real flow since\n // every Collection has `_internalDelete`.\n await outputColl.delete(priorId)\n deleted++\n }\n } catch (err) {\n failed++\n if (strict) throw err\n\n console.warn(`[mv] \"${spec.name}\" tombstone failed for id=\"${priorId}\":`, err)\n }\n }\n }\n\n return { written, deleted, failed, residueUndecodable, residueDeclined }\n },\n}\n\n/**\n * List ids currently present in the MV's output collection via the\n * adapter directly (avoids triggering the lazy resolve-on-read path\n * we're INSIDE). Returns an empty array if the collection doesn't\n * exist or the adapter doesn't surface a list method.\n *\n * @internal\n */\nasync function listOutputIds(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n outputColl: Collection<any>,\n): Promise<string[]> {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const cAny = outputColl as any\n const adapter = cAny.adapter as { list?: (v: string, c: string) => Promise<readonly string[]> }\n const vault = cAny.vault as string\n const name = cAny.name as string\n if (typeof adapter?.list !== 'function') return []\n try {\n const ids = await adapter.list(vault, name)\n return [...ids]\n } catch {\n return []\n }\n}\n"],"mappings":"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","names":[]}
@@ -1,99 +0,0 @@
1
- // src/kernel/query/predicate.ts
2
- function readPath(record, path) {
3
- if (record === null || record === void 0) return void 0;
4
- if (!path.includes(".")) {
5
- return record[path];
6
- }
7
- const segments = path.split(".");
8
- let cursor = record;
9
- for (const segment of segments) {
10
- if (cursor === null || cursor === void 0) return void 0;
11
- cursor = cursor[segment];
12
- }
13
- return cursor;
14
- }
15
- function evaluateFieldClause(record, clause) {
16
- const actual = readPath(record, clause.field);
17
- const { op, value } = clause;
18
- if (clause.via) return clause.via.evaluate(actual, op);
19
- switch (op) {
20
- case "==":
21
- return actual === value;
22
- case "!=":
23
- return actual !== value;
24
- case "<":
25
- return isComparable(actual, value) && actual < value;
26
- case "<=":
27
- return isComparable(actual, value) && actual <= value;
28
- case ">":
29
- return isComparable(actual, value) && actual > value;
30
- case ">=":
31
- return isComparable(actual, value) && actual >= value;
32
- case "in":
33
- return Array.isArray(value) && value.includes(actual);
34
- case "contains":
35
- if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
36
- if (Array.isArray(actual)) return actual.includes(value);
37
- return false;
38
- case "startsWith":
39
- return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
40
- case "between": {
41
- if (!Array.isArray(value) || value.length !== 2) return false;
42
- const [lo, hi] = value;
43
- if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
44
- return actual >= lo && actual <= hi;
45
- }
46
- default: {
47
- const _exhaustive = op;
48
- void _exhaustive;
49
- return false;
50
- }
51
- }
52
- }
53
- function isComparable(a, b) {
54
- if (typeof a === "number" && typeof b === "number") return true;
55
- if (typeof a === "string" && typeof b === "string") return true;
56
- if (a instanceof Date && b instanceof Date) return true;
57
- return false;
58
- }
59
- function evaluateClause(record, clause, fnRecord) {
60
- switch (clause.type) {
61
- case "field":
62
- return evaluateFieldClause(record, clause);
63
- case "filter":
64
- return clause.fn(fnRecord !== void 0 ? fnRecord : record);
65
- case "wherePredicate":
66
- return clause.fn(fnRecord !== void 0 ? fnRecord : record, clause.ctx);
67
- case "crossJoin":
68
- throw new Error(
69
- `evaluateClause: 'crossJoin' clauses are expansion primitives and are not evaluated per-record. This is a query planner routing error \u2014 crossJoin clauses must be extracted from the clause list before calling evaluateClause or filterRecords.`
70
- );
71
- case "group":
72
- if (clause.op === "and") {
73
- for (const child of clause.clauses) {
74
- if (!evaluateClause(record, child, fnRecord)) return false;
75
- }
76
- return true;
77
- } else {
78
- for (const child of clause.clauses) {
79
- if (evaluateClause(record, child, fnRecord)) return true;
80
- }
81
- return false;
82
- }
83
- }
84
- }
85
- function hasFnClause(clauses) {
86
- for (const c of clauses) {
87
- if (c.type === "filter" || c.type === "wherePredicate") return true;
88
- if (c.type === "group" && hasFnClause(c.clauses)) return true;
89
- }
90
- return false;
91
- }
92
-
93
- export {
94
- readPath,
95
- evaluateFieldClause,
96
- evaluateClause,
97
- hasFnClause
98
- };
99
- //# sourceMappingURL=chunk-NSITFOCJ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/query/predicate.ts"],"sourcesContent":["/**\n * Operator implementations for the query DSL.\n *\n * All predicates run client-side, AFTER decryption — they never see ciphertext.\n * Field clauses over a Via-covered field (e.g. money) carry their own\n * evaluator closure (see {@link FieldClause.via}), so this module has no\n * dependency on any Via feature's implementation — still tree-shakeable.\n */\n\n/** Comparison operators supported by the where() builder. */\nexport type Operator =\n | '=='\n | '!='\n | '<'\n | '<='\n | '>'\n | '>='\n | 'in'\n | 'contains'\n | 'startsWith'\n | 'between'\n\n/**\n * A single field comparison clause inside a query plan.\n * Plans are JSON-serializable, so this type uses primitives only.\n */\nexport interface FieldClause {\n readonly type: 'field'\n readonly field: string\n readonly op: Operator\n readonly value: unknown\n /**\n * Present when `field` is covered by a Via feature binding (e.g. a\n * declared money field): the opaque payload built by the binding's\n * `buildClause` at query BUILD time, plus a closure that evaluates it\n * per record via the binding's `evaluateClause`. Attached by\n * `Query.where()` / `ScanBuilder.where()` when `source.via` covers the\n * field. The closure keeps this module free of any dependency on a\n * Via feature's implementation — clauses are in-memory only, never\n * serialized, so carrying a function here is safe.\n */\n readonly via?: {\n readonly brand: string\n readonly payload: unknown\n readonly evaluate: (actual: unknown, op: string) => boolean\n /**\n * The binding's `indexProbe` result for this clause's op/payload\n * (#625) — the STORED-form operand `candidateRecords()` (`kernel/\n * query/builder.ts`) can hand to `CollectionIndexes.lookupEqual`/\n * `lookupIn` for a fast path. `undefined` (no `indexProbe` hook, or\n * the binding declined to probe this op) means the field must fall\n * back to a linear scan via `evaluate` above.\n */\n readonly indexValue?: unknown\n }\n}\n\n/**\n * A user-supplied predicate function escape hatch. Not serializable.\n *\n * The predicate accepts `unknown` at the type level so the surrounding\n * Clause type can stay non-parametric — this keeps Collection<T> covariant\n * in T at the public API surface. Builder methods cast user predicates\n * (typed `(record: T) => boolean`) into this shape on the way in.\n */\nexport interface FilterClause {\n readonly type: 'filter'\n readonly fn: (record: unknown) => boolean\n}\n\n/**\n * A declared deterministic predicate reference. The query\n * builder produces this via `.wherePredicate(name, ctx?)` when a\n * Query has been augmented with a predicates map (typically by the\n * materialized-view registry).\n *\n * `predicateHash` is the consumer-supplied stable hash for the\n * function body; `ctxHash` is the canonical-JSON SHA-256 of `ctx`.\n * Both fold into the MV's `queryHash` so a function or ctx change\n * forces refresh on next visit.\n *\n * `fn` is resolved at builder time from the predicates map and\n * embedded directly — so `evaluateClause` can fire it without a\n * runtime lookup.\n */\nexport interface WherePredicateClause {\n readonly type: 'wherePredicate'\n readonly name: string\n readonly ctx: unknown\n readonly predicateHash: string\n readonly ctxHash: string\n readonly fn: (record: unknown, ctx?: unknown) => boolean\n}\n\n/** A logical group of clauses combined by AND or OR. */\nexport interface GroupClause {\n readonly type: 'group'\n readonly op: 'and' | 'or'\n readonly clauses: readonly Clause[]\n}\n\n/**\n * Cartesian-product expansion clause. Appended to `QueryPlan.clauses`\n * by `Query.crossJoin()`. Processed in declaration order by\n * `executeClausePipeline` — NOT by `evaluateClause` (which is a\n * per-record predicate and throws on this type).\n */\nexport interface CrossJoinClause {\n readonly type: 'crossJoin'\n /** Target collection name to cross-join against. */\n readonly target: string\n /** Alias under which the right-side record is exposed on each result row. */\n readonly as: string\n /**\n * Lateral filter callback. `undefined` → full cartesian product.\n * Two call shapes:\n * - Subset: `(left) => TTarget[]` — returns the right rows for this left row\n * - Predicate: `(left) => (right) => boolean` — executor materializes then filters\n */\n readonly on?: (left: unknown) => unknown[] | ((right: unknown) => boolean)\n /** When `on:` was supplied as `{ predicate: name }`, the name is stored here for queryHash. */\n readonly onPredicateName?: string\n /** Per-clause row ceiling override. `undefined` → `DEFAULT_CROSS_JOIN_MAX_ROWS`. */\n readonly maxRows?: number\n /**\n * Left-outer mode (#1130). When the `on:` subset for a left row is empty,\n * emit that row once with `null` under `as` instead of dropping it.\n *\n * Applies to both call shapes. With `on:` the empty thing is that left\n * row's subset; without it, an empty TARGET collection is what would\n * otherwise drop every left row.\n */\n readonly outer?: boolean\n}\n\nexport type Clause = FieldClause | FilterClause | WherePredicateClause | GroupClause | CrossJoinClause\n\n/**\n * Read a possibly nested field path like \"address.city\" from a record.\n * Returns undefined if any segment is missing.\n */\nexport function readPath(record: unknown, path: string): unknown {\n if (record === null || record === undefined) return undefined\n if (!path.includes('.')) {\n return (record as Record<string, unknown>)[path]\n }\n const segments = path.split('.')\n let cursor: unknown = record\n for (const segment of segments) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n\n/**\n * Evaluate a single field clause against a record.\n * Returns false on type mismatches rather than throwing — query results\n * exclude non-matching records by definition.\n */\nexport function evaluateFieldClause(record: unknown, clause: FieldClause): boolean {\n const actual = readPath(record, clause.field)\n const { op, value } = clause\n\n // A Via-covered field (e.g. money) may store a transformed\n // representation (money: BigInt-exact in scaled-integer space) that the\n // generic paths below can't compare correctly — the operand was\n // pre-built at query BUILD time and the binding's own evaluator knows\n // how to compare it against the raw stored value.\n if (clause.via) return clause.via.evaluate(actual, op)\n\n switch (op) {\n case '==':\n return actual === value\n case '!=':\n return actual !== value\n case '<':\n return isComparable(actual, value) && (actual as number) < (value as number)\n case '<=':\n return isComparable(actual, value) && (actual as number) <= (value as number)\n case '>':\n return isComparable(actual, value) && (actual as number) > (value as number)\n case '>=':\n return isComparable(actual, value) && (actual as number) >= (value as number)\n case 'in':\n return Array.isArray(value) && value.includes(actual)\n case 'contains':\n if (typeof actual === 'string') return typeof value === 'string' && actual.includes(value)\n if (Array.isArray(actual)) return actual.includes(value)\n return false\n case 'startsWith':\n return typeof actual === 'string' && typeof value === 'string' && actual.startsWith(value)\n case 'between': {\n if (!Array.isArray(value) || value.length !== 2) return false\n const [lo, hi] = value\n if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false\n return (actual as number) >= (lo as number) && (actual as number) <= (hi as number)\n }\n default: {\n // Exhaustiveness — TS will error if a new operator is added without a case.\n const _exhaustive: never = op\n void _exhaustive\n return false\n }\n }\n}\n\n/**\n * Two values are \"comparable\" if they share an order-defined runtime type.\n * Strings compare lexicographically; numbers and Dates numerically; otherwise false.\n */\nfunction isComparable(a: unknown, b: unknown): boolean {\n if (typeof a === 'number' && typeof b === 'number') return true\n if (typeof a === 'string' && typeof b === 'string') return true\n if (a instanceof Date && b instanceof Date) return true\n return false\n}\n\n/**\n * Evaluate any clause (field / filter / group) against a record.\n * The recursion depth is bounded by the user's query expression — no risk of\n * blowing the stack on a 50K-record collection.\n *\n * `fnRecord`, when provided, is the view handed to USER CALLBACK clauses\n * (`filter` / `wherePredicate`) instead of `record` — the executor passes\n * the money-decoded view there so user code never sees the stored\n * scaled-int form, while field clauses keep evaluating against the raw\n * record (their money operands are pre-quantized to that space).\n */\nexport function evaluateClause(record: unknown, clause: Clause, fnRecord?: unknown): boolean {\n switch (clause.type) {\n case 'field':\n return evaluateFieldClause(record, clause)\n case 'filter':\n return clause.fn(fnRecord !== undefined ? fnRecord : record)\n case 'wherePredicate':\n return clause.fn(fnRecord !== undefined ? fnRecord : record, clause.ctx)\n case 'crossJoin':\n throw new Error(\n `evaluateClause: 'crossJoin' clauses are expansion primitives and are not ` +\n `evaluated per-record. This is a query planner routing error — ` +\n `crossJoin clauses must be extracted from the clause list before calling ` +\n `evaluateClause or filterRecords.`,\n )\n case 'group':\n if (clause.op === 'and') {\n for (const child of clause.clauses) {\n if (!evaluateClause(record, child, fnRecord)) return false\n }\n return true\n } else {\n for (const child of clause.clauses) {\n if (evaluateClause(record, child, fnRecord)) return true\n }\n return false\n }\n }\n}\n\n/**\n * Does the clause list contain any user-callback clause (filter /\n * wherePredicate), at any group nesting depth? Used by executors to\n * decide whether the per-record decoded view needs materializing.\n */\nexport function hasFnClause(clauses: readonly Clause[]): boolean {\n for (const c of clauses) {\n if (c.type === 'filter' || c.type === 'wherePredicate') return true\n if (c.type === 'group' && hasFnClause(c.clauses)) return true\n }\n return false\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-audit/periods/periods.ts","../src/with-audit/periods/active.ts","../src/with-audit/periods/strategy.ts"],"sourcesContent":["/**\n * Accounting-period closure + opening.\n *\n * A closed period seals every record whose envelope `_ts` is at or\n * before the period's `endDate`: further writes (`put` / `delete`)\n * against such records throw {@link PeriodClosedError}. The period\n * itself is stored as a record in the reserved `_periods` collection\n * and written through the normal ledger-instrumented path, so every\n * closure appends a tamper-evident entry to the vault's hash chain.\n *\n * ## Closure model\n *\n * ```\n * vault.closePeriod({ name: 'FY2026-Q1', endDate: '2026-03-31' })\n * └─► PeriodRecord written to _periods/<name>\n * ├─ priorPeriodName / priorPeriodHash — chain to last close\n * ├─ closedAt / closedBy — provenance\n * └─ normal ledger append fires (LedgerStore.append)\n * ```\n *\n * Enforcement (`assertTsWritable`) is vault-local: the Vault caches\n * the list of closed periods on first read and consults that cache in\n * the `Collection.put` / `.delete` path via the `periodGuard` hook.\n *\n * ## Opening model\n *\n * ```\n * vault.openPeriod({\n * name: 'FY2026-Q2',\n * startDate: '2026-04-01',\n * fromPeriod: 'FY2026-Q1',\n * carryForward: async (priorView) => Record<string, Record<string, unknown>>,\n * })\n * ```\n *\n * `carryForward` receives a read-only `VaultInstant` anchored at the\n * prior period's `endDate` (built via `vault.at(endDate)`) so the\n * callback can compute closing aggregates from the sealed state. The\n * returned `{ [collectionName]: { [id]: record } }` map is written\n * before the new `PeriodRecord` lands — opening balances materialise\n * as normal records with fresh timestamps that fall outside every\n * closed period.\n *\n * ## Freeze\n *\n * ```\n * vault.freezePeriod('FY2026-Q1')\n * └─► physically purges delete markers whose write-time falls inside\n * the closed period's window (via the #589 `_purgeDeleteMarkers`\n * seam), then records the fact:\n * ├─ PeriodFreezeRecord written to _period_freezes/<name>\n * └─ normal ledger append fires (LedgerStore.append)\n * ```\n *\n * The chained `_periods/<name>` record is never mutated — `frozenAt` /\n * `frozenBy` / `purgedMarkerCount` are merged onto the returned\n * `PeriodRecord` at read time from the companion, so a tamper with the\n * freeze can never break the inter-period hash chain. Freezing is\n * terminal (a closed period, once frozen, stays frozen) and idempotent\n * (a second call is a no-op that returns the same merged record without\n * re-purging or re-appending a ledger entry). Freeze does NOT purge\n * forget-tombstones (GDPR crypto-shred erasure evidence), `_history`\n * versions, or live records — the delete-markers-only seam leaves all\n * three untouched by construction.\n *\n * Freeze purges the LOCAL adapter only. On a synced vault, markers already\n * pushed to sync targets survive there, and a later pull re-imports them\n * (benign — they still read deleted, but the space isn't reclaimed). A\n * re-imported marker keeps its original `_ts` (inside the already-frozen\n * period's window), so — like any late-booked delete — it is reclaimed by\n * the NEXT period's freeze, whose window covers it; freeze stays terminal\n * and does NOT re-purge an already-frozen period (#611). Sweeping the sync\n * targets themselves is a cross-target-purge concern deferred to the\n * cold-archival spec. Purging re-opens the #589 resurrection window for a\n * peer offline since before the cutoff, which is why the closed period is\n * the operator-asserted safe-point that gates the call.\n *\n * A period whose purge window has not fully elapsed cannot be frozen —\n * `freezePeriod` throws rather than purge markers for deletes that may not\n * have converged yet (#610).\n *\n * ## Archive\n *\n * ```\n * vault.archivePeriod('FY2026-Q1')\n * └─► relocates the closed period's in-window records (those with\n * `_ts < periodExclusiveUpperBound(endDate)`) from the hot store to\n * the configured cold tier (routeStore's `cold` route), then records:\n * ├─ PeriodArchiveRecord written to _period_archives/<name>\n * └─ a ledger entry attributed to _period_archives\n * ```\n *\n * Archival is NON-DESTRUCTIVE: routeStore reads fall through to the cold\n * tier on a hot miss, so an archived record still reads normally. It is\n * therefore gated only on `closed` (not `frozen`) — it does not re-open the\n * #589 resurrection window and needs no convergence safe-point. Freeze\n * (purge markers) and archive (relocate records) are independent and compose\n * in either order. Like freeze, archival keeps the chained `_periods/<name>`\n * record byte-immutable (state lives in the companion) and is idempotent.\n *\n * Bounds by write-time `_ts`, NOT business date: the store tier sees only\n * encrypted envelopes. A record with an in-period business date but a later\n * `_ts` (late-booked) archives at the NEXT period's archive — the same rule\n * freeze uses for late-booked delete markers. Requires a `routeStore` with a\n * cold route (`age: { cold }`); throws otherwise.\n *\n * Read cost: with `withLazy()` (per-id reads) archived records are truly\n * cold — fetched from cold only on access. In the default hydrated mode,\n * `loadAll` merges the cold store, so archived records still load into RAM\n * on vault open (hot-tier STORAGE is reclaimed; RAM is not). Summaries\n * (`_`-prefixed) always stay hot.\n *\n * ## Target-purge\n *\n * ```\n * vault.purgePeriodTargets('FY2026-Q1')\n * └─► sweeps delete markers (`_ts < periodExclusiveUpperBound(endDate)`) off\n * the vault's PUSH-ONLY sync targets (backup/archive roles), then records:\n * ├─ PeriodTargetPurgeRecord written to _period_target_purges/<name>\n * └─ a ledger entry attributed to _period_target_purges\n * ```\n *\n * Extends freeze's local marker purge to the vault's own remote sinks.\n * `sync-peer` (bidirectional) targets are SKIPPED: purging a marker there\n * re-opens the #589 resurrection window for a client offline before the\n * cutoff, an assertion no single vault can verify. Backup/archive targets are\n * push-only — never pulled from into convergence — so sweeping their markers\n * is safe. Requires the period be frozen first (closed → frozen →\n * target-purged) so the local safe-point is already established. Idempotent\n * once run; a vault with no push-only targets writes no companion and is\n * re-runnable (so a target added later is still swept). Single-vault only —\n * fleet-wide purge across sovereign vaults is klum's concern over\n * `@noy-db/hub/cargo`.\n *\n * ## Not covered\n *\n * - Partial re-opening of a closed period. If an auditor needs to\n * make a correction inside a sealed period, the sanctioned path is\n * a compensating entry in the NEW period, not an unlock of the\n * old one.\n * - Automatic period rollover. `closePeriod` / `openPeriod` are\n * deliberately explicit operator calls so the caller decides when\n * the boundary lands.\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport type { LedgerStore } from '../../with-commit/history/ledger/index.js'\nimport { sha256Hex, canonicalJson } from '../../with-commit/history/ledger/index.js'\nimport { isDeleteMarker } from '../../kernel/enclave/index.js'\nimport { PeriodClosedError, ValidationError } from '../../kernel/errors.js'\n\n// The reserved collection names + `periodExclusiveUpperBound` moved to the\n// dependency-light `window.ts` so the sync engine's period-scoped pull (#807)\n// can import them without this module's ledger imports; re-exported here so\n// every existing import path keeps working.\nexport {\n PERIODS_COLLECTION,\n PERIOD_FREEZES_COLLECTION,\n PERIOD_ARCHIVES_COLLECTION,\n PERIOD_TARGET_PURGES_COLLECTION,\n PERIOD_REOPENS_COLLECTION,\n periodExclusiveUpperBound,\n} from './window.js'\nimport { PERIODS_COLLECTION } from './window.js'\n\n/**\n * Companion record recording that a closed period was frozen (its delete\n * markers physically purged). Stored in {@link PERIOD_FREEZES_COLLECTION},\n * keyed by period name — kept OFF the hash-chained `_periods/<name>` record so\n * freeze never alters the inter-period chain.\n */\nexport interface PeriodFreezeRecord {\n readonly period: string\n readonly frozenAt: string\n readonly frozenBy: string\n readonly purgedMarkerCount: number\n}\n\n/**\n * Companion record noting that a closed period was cold-archived (its\n * in-window records physically relocated hot → cold). Stored in\n * {@link PERIOD_ARCHIVES_COLLECTION}, keyed by period name — kept OFF the\n * hash-chained `_periods/<name>` record so archive never alters the chain.\n */\nexport interface PeriodArchiveRecord {\n readonly period: string\n readonly archivedAt: string\n readonly archivedBy: string\n readonly archivedRecordCount: number\n}\n\n/** Per-target count of delete markers purged off one push-only sync target (#615). */\nexport interface TargetPurgeCount {\n readonly label?: string\n readonly role: 'backup' | 'archive'\n readonly purgedCount: number\n}\n\n/**\n * One entry in a period's append-only reopen/reclose log (#1022).\n *\n * Real accounting close is a three-state lifecycle — open / closed / reopened\n * — not a one-way door. A month gets closed, a missing invoice arrives or a\n * filing is rejected, the month is reopened, corrected, and reclosed. The audit\n * value is not the ability to write again; it is the chain being able to say\n * *closed at T1, reopened at T2 by U, reclosed at T3*.\n */\nexport interface PeriodReopenEvent {\n readonly op: 'reopen' | 'reclose'\n /** ISO timestamp the event was recorded. */\n readonly at: string\n /** userId of the keyring that performed it. */\n readonly by: string\n /**\n * `reopen` only — ISO instant after which the period re-seals on its own,\n * with nobody acting. Absent means the window stays open until an explicit\n * `reclosePeriod`.\n */\n readonly until?: string\n /** Free-text justification, carried verbatim into the audit trail. */\n readonly reason?: string\n}\n\n/**\n * Companion holding a period's reopen/reclose history (#1022). Stored in\n * {@link PERIOD_REOPENS_COLLECTION}, keyed by the period's storage key — kept\n * OFF the hash-chained `_periods/<name>` record for the same reason freeze and\n * archive are: reopening must never rewrite the close, or the chain that proves\n * the close happened is the very thing the reopen destroys.\n *\n * `events` is APPEND-ONLY. Where the other companions are single-shot and\n * idempotent, this one accumulates, because the cycle repeats.\n */\nexport interface PeriodReopenRecord {\n readonly period: string\n readonly partition?: PeriodPartition\n readonly events: readonly PeriodReopenEvent[]\n}\n\n/**\n * Collapse an append-only reopen log into the return-only fields merged onto a\n * {@link PeriodRecord} on read.\n *\n * Expiry is deliberately NOT resolved here: `reopenedUntil` is carried through\n * verbatim and compared against the clock at write-guard time, so a bounded\n * window re-seals on its own without anything having to run.\n *\n * @internal\n */\nexport function resolveReopenState(events: readonly PeriodReopenEvent[]): {\n reopenedAt?: string\n reopenedBy?: string\n reopenedUntil?: string\n reopenReason?: string\n reclosedAt?: string\n reopenCount: number\n} {\n let lastReopen: PeriodReopenEvent | undefined\n let reclosedAfter: string | undefined\n let reopenCount = 0\n for (const e of events) {\n if (e.op === 'reopen') {\n lastReopen = e\n reclosedAfter = undefined\n reopenCount++\n } else if (lastReopen !== undefined) {\n reclosedAfter = e.at\n }\n }\n if (!lastReopen) return { reopenCount }\n return {\n reopenedAt: lastReopen.at,\n reopenedBy: lastReopen.by,\n ...(lastReopen.until !== undefined && { reopenedUntil: lastReopen.until }),\n ...(lastReopen.reason !== undefined && { reopenReason: lastReopen.reason }),\n ...(reclosedAfter !== undefined && { reclosedAt: reclosedAfter }),\n reopenCount,\n }\n}\n\n/**\n * Is this period writable right now on account of a reopen? (#1022)\n *\n * Three ways to be sealed again: never reopened, explicitly reclosed after the\n * last reopen, or a bounded window that has elapsed. The clock is read by the\n * caller and passed in, so the guard and any diagnostic agree on one instant.\n *\n * @internal\n */\nexport function isEffectivelyReopened(period: PeriodRecord, nowIso: string): boolean {\n if (period.reopenedAt === undefined) return false\n if (period.reclosedAt !== undefined && period.reclosedAt >= period.reopenedAt) return false\n if (period.reopenedUntil !== undefined && nowIso > period.reopenedUntil) return false\n return true\n}\n\n/**\n * Companion record noting that a closed+frozen period's delete markers were\n * swept off the vault's push-only sync targets (#615). Stored in\n * {@link PERIOD_TARGET_PURGES_COLLECTION}, keyed by period name — kept OFF the\n * hash-chained `_periods/<name>` record so target-purge never alters the chain.\n */\nexport interface PeriodTargetPurgeRecord {\n readonly period: string\n readonly purgedAt: string\n readonly purgedBy: string\n readonly targets: readonly TargetPurgeCount[]\n}\n\n/**\n * Scope tuple for a period timeline (#1005).\n *\n * Identical in shape and semantics to `SequenceOptions.partition`: a\n * partitioned timeline is always disjoint from any unpartitioned one, and from\n * every other tuple. `['acme', 'vat']` and `['acme', 'wht']` are two\n * independent close calendars for the same subject — which is the whole point,\n * since sub-ledgers for one legal entity and one month routinely close on\n * different statutory deadlines.\n */\nexport type PeriodPartition = readonly (string | number)[]\n\n/**\n * Resolve the `_periods` storage key for a (name, partition) pair.\n *\n * Deliberately the same encoding as `resolveSequenceKey`: `name` verbatim when\n * unpartitioned, else `${name}\\x00${parts}` with each component\n * `encodeURIComponent`d and `'/'`-joined. The null-byte separator cannot occur\n * in a period name, so a partitioned key never collides with an unpartitioned\n * one; URI-encoding keeps `['a/b']` distinct from `['a','b']`.\n *\n * @throws {ValidationError} on an empty component or a non-finite number.\n * @internal\n */\nexport function resolvePeriodKey(name: string, partition?: PeriodPartition): string {\n if (!partition || partition.length === 0) return name\n const parts = partition.map((p) => {\n if (typeof p === 'number' && !Number.isFinite(p)) {\n throw new ValidationError(`period partition component must be a finite number, got ${p}`)\n }\n const s = String(p)\n if (s === '') {\n throw new ValidationError('period partition component must not be empty')\n }\n return encodeURIComponent(s)\n })\n return `${name}\\x00${parts.join('/')}`\n}\n\n/**\n * Do two partitions denote the same timeline? Absent and empty both mean \"the\n * unpartitioned timeline\", so they compare equal.\n *\n * @internal\n */\nexport function samePartition(a?: PeriodPartition, b?: PeriodPartition): boolean {\n const x = a ?? []\n const y = b ?? []\n if (x.length !== y.length) return false\n return x.every((v, i) => String(v) === String(y[i]))\n}\n\n/**\n * Resolves a record to the timeline that governs it. Supplied by\n * `withPeriods({ subjects })`; returns `undefined` for any collection with no\n * mapping, which is what keeps an unconfigured vault on the single vault-wide\n * timeline it has always had.\n *\n * @internal\n */\nexport type PartitionResolver = (\n collection: string,\n record: Record<string, unknown>,\n) => PeriodPartition | undefined\n\n/**\n * Stored record for one closed or opened accounting period. One entry\n * per period, keyed by `name` in the reserved `_periods` collection.\n *\n * The hash chain between periods is computed at read time by\n * `loadPeriods()` — each record carries the name + hash of its\n * predecessor so a tamper with any period's record breaks the chain\n * into the next one, the same way the ledger's `prevHash` works.\n */\nexport interface PeriodRecord {\n /**\n * Human-readable name (e.g., `'FY2026-Q1'`). Unique per PARTITION — two\n * timelines may each carry a `'2026-06'`, which is the normal case when one\n * vault serves several subjects (#1005). Unique per vault when unpartitioned.\n */\n readonly name: string\n /**\n * The timeline this period belongs to. Absent = the vault-wide timeline.\n * Two periods with the same `name` and different `partition` are unrelated:\n * separate hash chains, separate close state, and the write guard applies\n * each only to records that resolve to its own tuple.\n */\n readonly partition?: PeriodPartition\n /**\n * Role discriminator. A period is `'closed'` from the moment its\n * `closedAt` is recorded; `'opened'` marks a period whose opening\n * entries have been carried forward via {@link openPeriod}. Many\n * workflows will produce one opened period per closed period (the\n * opened one is the SUCCESSOR — its `startDate` equals the prior\n * `endDate + 1 day`).\n */\n readonly kind: 'closed' | 'opened'\n /** ISO date — inclusive upper bound for records belonging to this period. */\n readonly endDate: string\n /** ISO date — lower bound (present on opened periods only). */\n readonly startDate?: string\n /**\n * Record field carrying the business date (e.g. `'date'` on an\n * invoice, `'paidAt'` on a payment). The guard compares\n * `record[dateField]` against `endDate` — NOT the envelope `_ts`.\n * Accounting entries booked late (business date `2026-01-15`,\n * write-time `2026-04-22`) still get sealed when Q1 closes at\n * `2026-03-31` because the comparison uses the business date.\n *\n * Optional for backwards compat. When absent, the guard falls back\n * to envelope `_ts` — that's a write-time seal, appropriate for\n * content that doesn't carry a logical business date (e.g. system\n * settings) but almost never right for accounting ledgers.\n */\n readonly dateField?: string\n /** ISO timestamp recorded at `closePeriod()` / `openPeriod()` call time. */\n readonly closedAt: string\n /** userId of the keyring that invoked the close/open. */\n readonly closedBy: string\n /** Name of the prior period this one chains to, if any. */\n readonly priorPeriodName?: string\n /** sha256(canonicalJson(priorPeriod)) — empty for the first period. */\n readonly priorPeriodHash: string\n /**\n * Opened periods only — the names of the collections whose\n * carry-forward aggregates were written by {@link openPeriod}.\n * Recorded for auditability so a future `verifyPeriodChain()` can\n * cross-check the opening balances against the closing snapshot.\n */\n readonly openingCollections?: readonly string[]\n /** #604 return-only — merged from the `_period_freezes/<name>` companion on\n * read; NEVER written into the stored `_periods/<name>` record (would break\n * the hash chain). Absent = not yet frozen. */\n readonly frozenAt?: string\n readonly frozenBy?: string\n readonly purgedMarkerCount?: number\n /** #613 return-only — merged from the `_period_archives/<name>` companion on\n * read; NEVER written into the stored `_periods/<name>` record. Absent = not\n * yet archived. */\n readonly archivedAt?: string\n readonly archivedBy?: string\n readonly archivedRecordCount?: number\n /** #615 return-only — merged from the `_period_target_purges/<name>` companion\n * on read; NEVER written into the stored `_periods/<name>` record. Absent =\n * target-purge not yet run (or the vault has no push-only targets). */\n readonly targetsPurgedAt?: string\n readonly targetsPurgedBy?: string\n readonly targetsPurged?: readonly TargetPurgeCount[]\n /** #1022 return-only — collapsed from the `_period_reopens/<key>` append-only\n * log on read; NEVER written into the stored `_periods/<name>` record, so a\n * reopen cannot disturb the inter-period hash chain. Absent = never reopened.\n * `reclosedAt` present (and >= `reopenedAt`) means the window was closed\n * again explicitly; `reopenedUntil` in the past means it lapsed on its own.\n * Use {@link isEffectivelyReopened} rather than reading these directly. */\n readonly reopenedAt?: string\n readonly reopenedBy?: string\n readonly reopenedUntil?: string\n readonly reopenReason?: string\n readonly reclosedAt?: string\n /** How many times this period has been reopened, ever. */\n readonly reopenCount?: number\n}\n\n/** Options for `vault.closePeriod()`. */\nexport interface ClosePeriodOptions {\n /** Human-readable name. Must not collide with an existing period. */\n readonly name: string\n /**\n * Inclusive upper cutoff. A record is sealed when its\n * `record[dateField]` (or, if absent, the envelope `_ts`) is at or\n * before this ISO timestamp.\n */\n readonly endDate: string\n /**\n * Record field carrying the business date used for period\n * membership. Recommended for accounting workflows — e.g. an\n * invoice booked late (write-time after close) is still sealed\n * when its `invoice.date` falls inside the closed period.\n *\n * Omit to use envelope `_ts` (write-time seal). This fallback\n * rarely matches real-world accounting semantics; prefer passing\n * an explicit `dateField`.\n */\n readonly dateField?: string\n /**\n * Close only this timeline (#1005). Omit for the vault-wide timeline.\n *\n * ```ts\n * vault.closePeriod({\n * name: '2026-06', endDate: '2026-06-30', dateField: 'issuedAt',\n * partition: [clientId, 'vat'],\n * })\n * ```\n *\n * Which records the resulting seal applies to is decided by the\n * `subjects` map passed to `withPeriods()` — without one, no record ever\n * resolves to a partition and a partitioned close seals nothing.\n */\n readonly partition?: PeriodPartition\n}\n\n/** Options for `vault.openPeriod()`. */\nexport interface OpenPeriodOptions<TCollections = Record<string, Record<string, unknown>>> {\n /** Human-readable name for the new period. Must be unique. */\n readonly name: string\n /**\n * The timeline to open in. Must match the partition of `fromPeriod` — a\n * period cannot chain across timelines, since each carries its own hash\n * chain (#1005).\n */\n readonly partition?: PeriodPartition\n /** ISO lower bound of the new period (usually prior `endDate + 1 day`). */\n readonly startDate: string\n /**\n * Name of the prior CLOSED period this one chains from. The prior\n * period's record is verified to exist and to be `kind: 'closed'`;\n * its `endDate` is made available to the `carryForward` callback.\n */\n readonly fromPeriod: string\n /**\n * Receives a read-only facade over the vault's CURRENT state,\n * plus the prior period's `endDate`. Accounting semantics: after\n * a period closes, records with `record[dateField] <= endDate`\n * are frozen — current state equals closing state, so a caller\n * can compute closing balances by querying the live collection\n * with a `where('date', '<=', priorEndDate)` filter.\n *\n * Returns opening-balance records keyed by collection name.\n * Example:\n *\n * ```ts\n * carryForward: async (ctx) => {\n * const closing = await ctx.collection<Journal>('journal')\n * .query().where('date', '<=', ctx.priorEndDate).toArray()\n * const opening: Record<string, Journal> = {}\n * for (const entry of closing) {\n * opening[`OB-${entry.id}`] = { ...entry, date: '2026-04-01' }\n * }\n * return { journal: opening }\n * }\n * ```\n */\n readonly carryForward: (\n ctx: CarryForwardContext,\n ) => Promise<TCollections> | TCollections\n}\n\n/**\n * Context passed to `OpenPeriodOptions.carryForward`. Exposes a\n * read-only subset of the live vault (`collection(name).get/list`)\n * plus the prior period's `endDate` so business-date filters can\n * be built by the caller.\n *\n * Writes go via the return value, not via the facade — the\n * `collection()` here is deliberately restricted to reads.\n */\nexport interface CarryForwardContext {\n /** The prior period's `endDate` — the boundary of the closing snapshot. */\n readonly priorEndDate: string\n /** Read-only collection facade over current vault state. */\n collection<T = unknown>(name: string): ReadOnlyCollection<T>\n}\n\n/** Minimum read surface exposed to `carryForward`. */\nexport interface ReadOnlyCollection<T> {\n get(id: string): Promise<T | null>\n list(): Promise<T[]>\n}\n\n/**\n * Load every period record currently stored on the adapter.\n * Decrypting is the caller's responsibility (we return plain records\n * so the vault can use its own `_periods` DEK).\n *\n * @internal — called by Vault methods that need the closed-period\n * cache. Not part of the public API surface.\n */\nexport async function loadPeriods(\n adapter: NoydbStore,\n vault: string,\n decrypt: (id: string, envelope: EncryptedEnvelope) => Promise<PeriodRecord>,\n): Promise<PeriodRecord[]> {\n const ids = await adapter.list(vault, PERIODS_COLLECTION)\n const records: PeriodRecord[] = []\n for (const id of ids) {\n const env = await adapter.get(vault, PERIODS_COLLECTION, id)\n if (env) records.push(await decrypt(id, env))\n }\n // Stable order by closedAt so chain verification is reproducible.\n records.sort((a, b) => a.closedAt.localeCompare(b.closedAt))\n return records\n}\n\n/**\n * Given the current ordered period list, pick the last entry that\n * belongs to the hash chain — used as the `priorPeriodHash` anchor\n * for the next closure/opening.\n *\n * @internal\n */\nexport async function chainAnchor(\n records: readonly PeriodRecord[],\n partition?: PeriodPartition,\n): Promise<{ priorPeriodName?: string; priorPeriodHash: string }> {\n // #1005 — each timeline carries its OWN chain. Anchoring a partitioned close\n // to whatever happened to be written last vault-wide would interleave\n // unrelated subjects into one chain, so verifying client A's June would\n // depend on client B never having closed in between.\n const inTimeline = records.filter((p) => samePartition(p.partition, partition))\n const last = inTimeline[inTimeline.length - 1]\n if (!last) return { priorPeriodHash: '' }\n const hash = await sha256Hex(canonicalJson(last as unknown as Record<string, unknown>))\n return { priorPeriodName: last.name, priorPeriodHash: hash }\n}\n\n/**\n * Throw `PeriodClosedError` if the record being touched falls within\n * any closed period.\n *\n * Three signals, evaluated per period:\n *\n * 1. If the period declares a `dateField`, the guard reads\n * `record[dateField]` on BOTH the existing (prior) record AND the\n * incoming (new) record. Either comparing `<= endDate` triggers\n * the error — callers cannot slide a record into a closed period\n * by editing its date field.\n * 2. If the period has no `dateField`, the guard falls back to the\n * envelope `_ts` of the existing record. Fresh inserts (no\n * existing envelope) pass.\n * 3. For a delete, only the existing side is checked.\n *\n * @internal\n */\nexport function assertTsWritable(\n existing: { ts: string | null; record: Record<string, unknown> | null } | null,\n incomingRecord: Record<string, unknown> | null,\n closedPeriods: readonly PeriodRecord[],\n scope?: { collection: string; resolve?: PartitionResolver },\n): void {\n // #1005 — a period only governs records that resolve to ITS timeline. With no\n // resolver (the default `withPeriods()`), nothing resolves to a partition, so\n // every record sits on the vault-wide timeline exactly as before and a\n // partitioned period governs nothing.\n const partitionOf = (r: Record<string, unknown> | null): PeriodPartition | undefined => {\n if (!r || !scope?.resolve) return undefined\n return scope.resolve(scope.collection, r)\n }\n const existingRecord = existing?.record ?? null\n const existingPartition = partitionOf(existingRecord)\n const incomingPartition = partitionOf(incomingRecord)\n // One instant for the whole check, so a bounded reopen window cannot expire\n // between two periods in the same loop and seal a write half-way.\n const now = new Date().toISOString()\n\n for (const p of closedPeriods) {\n if (p.kind !== 'closed') continue\n // #1022 — a reopened period is writable again. This is the ONLY thing a\n // reopen does: it withdraws the period's veto. It cannot grant a write that\n // some other gate forbids, because the guard bus ANDs every handler and\n // record-level guards are registered ahead of this one.\n if (isEffectivelyReopened(p, now)) continue\n if (p.dateField) {\n const checkRecord = (\n label: string,\n r: Record<string, unknown> | null,\n recordPartition: PeriodPartition | undefined,\n ): void => {\n if (!r) return\n // Both sides are checked under their OWN partition, which is what stops\n // a write from sliding a record either INTO or OUT OF a sealed\n // timeline by rewriting the fields the subject mapping reads.\n if (!samePartition(recordPartition, p.partition)) return\n const v = r[p.dateField!]\n if (typeof v === 'string' && v <= p.endDate) {\n throw new PeriodClosedError(p.name, p.endDate, `${label}[${p.dateField}]=${v}`)\n }\n }\n checkRecord('existing', existingRecord, existingPartition)\n checkRecord('incoming', incomingRecord, incomingPartition)\n continue\n }\n // Fallback: write-time seal via envelope _ts. Scoped by the EXISTING\n // record's partition — `_ts` belongs to the stored envelope, so the\n // incoming side has no write-time of its own to compare.\n if (!samePartition(existingPartition, p.partition)) continue\n const existingTs = existing?.ts ?? null\n if (existingTs !== null && existingTs <= p.endDate) {\n throw new PeriodClosedError(p.name, p.endDate, existingTs)\n }\n }\n}\n\n/**\n * Sanity-check a proposed period name + endDate against existing\n * records. Shared by closePeriod / openPeriod so the two pathways\n * produce identical diagnostics.\n *\n * @internal\n */\nexport function validatePeriodName(\n name: string,\n existing: readonly PeriodRecord[],\n partition?: PeriodPartition,\n): void {\n if (name.length === 0) {\n throw new ValidationError('Period name cannot be empty.')\n }\n // Validates the components as a side effect — an empty or non-finite\n // component must be rejected at the call, not encoded into a storage key.\n resolvePeriodKey(name, partition)\n // #1005 — uniqueness is per TIMELINE. `'2026-06'` in `['A','vat']` does not\n // collide with `'2026-06'` in `['B','vat']`.\n if (existing.some((p) => p.name === name && samePartition(p.partition, partition))) {\n const where = partition && partition.length > 0\n ? ` in partition [${partition.join(', ')}]`\n : ''\n throw new ValidationError(`Period \"${name}\" already exists${where}.`)\n }\n}\n\n/**\n * Wire a reserved-collection ledger append for a period record. The\n * period itself is stored via the adapter as an encrypted envelope;\n * the ledger entry is a normal `put` with the period's payloadHash,\n * so period closures inherit the chain's tamper-evidence.\n *\n * @internal\n */\nexport async function appendPeriodLedgerEntry(\n ledger: LedgerStore | null,\n actor: string,\n envelope: EncryptedEnvelope,\n name: string,\n collection: string = PERIODS_COLLECTION,\n): Promise<void> {\n if (!ledger) return\n const { envelopePayloadHash } = await import('../../with-commit/history/ledger/index.js')\n await ledger.append({\n op: 'put',\n collection,\n id: name,\n version: envelope._v,\n actor,\n payloadHash: await envelopePayloadHash(envelope),\n })\n}\n\n/**\n * @internal #615. Sweep delete markers with `_ts < before` off ANY store\n * (the vault's local adapter, or a push-only sync target). Returns the count\n * removed. Shared by `vault._purgeDeleteMarkers` (local) and\n * `vault._purgePeriodTargets` (push-only targets).\n */\nexport async function purgeMarkersOn(\n store: NoydbStore,\n vault: string,\n before: string,\n collections?: string[],\n): Promise<number> {\n const snapshot = await store.loadAll(vault)\n let removed = 0\n for (const [coll, records] of Object.entries(snapshot)) {\n if (collections && !collections.includes(coll)) continue\n for (const [id, env] of Object.entries(records)) {\n if (isDeleteMarker(env) && env._ts < before) {\n await store.delete(vault, coll, id)\n removed++\n }\n }\n }\n return removed\n}\n","/**\n * Active periods strategy factory. Only reachable through the\n * `@noy-db/hub/periods` subpath.\n */\n\nimport {\n loadPeriods,\n chainAnchor,\n assertTsWritable,\n validatePeriodName,\n appendPeriodLedgerEntry,\n} from './periods.js'\nimport type { PeriodPartition, PartitionResolver } from './periods.js'\nimport type { PeriodsStrategy } from './strategy.js'\n\n/** Options for {@link withPeriods}. */\nexport interface WithPeriodsOptions {\n /**\n * Maps a collection to the timeline each of its records belongs to (#1005) —\n * the answer to \"which close calendar governs THIS record\".\n *\n * ```ts\n * withPeriods({\n * subjects: { receipts: (r) => [r.clientId, layerOf(r)] },\n * })\n * ```\n *\n * Same shape as `withForget({ subjects })`, which answers the same question\n * for erasure. A collection with no entry — and every collection when\n * `subjects` is omitted entirely — stays on the vault-wide timeline, so an\n * existing vault behaves exactly as it did before partitions existed.\n *\n * Return `undefined` from a mapper to put an individual record back on the\n * vault-wide timeline (e.g. a record that predates the field the mapping\n * reads).\n */\n readonly subjects?: Readonly<\n Record<string, (record: Record<string, unknown>) => PeriodPartition | undefined>\n >\n}\n\n/**\n * Build the default periods strategy. Pass into\n * `createNoydb({ periodsStrategy: withPeriods() })` to enable\n * `vault.closePeriod()` / `vault.openPeriod()` / write-guards.\n *\n * Pass `subjects` to run more than one close calendar in a single vault — see\n * {@link WithPeriodsOptions.subjects}.\n */\nexport function withPeriods(options?: WithPeriodsOptions): PeriodsStrategy {\n const subjects = options?.subjects\n const partitionOf: PartitionResolver | undefined = subjects\n ? (collection, record) => subjects[collection]?.(record)\n : undefined\n\n return {\n loadPeriods,\n chainAnchor,\n assertTsWritable,\n validatePeriodName,\n appendPeriodLedgerEntry,\n ...(partitionOf !== undefined && { partitionOf }),\n }\n}\n","/**\n * Strategy seam for the optional accounting-periods service. Core\n * imports `PeriodsStrategy` type-only + `NO_PERIODS` stub; the real\n * `loadPeriods` / `chainAnchor` / `assertTsWritable` /\n * `validatePeriodName` / `appendPeriodLedgerEntry` functions are\n * only reachable via `withPeriods()` in `./active.ts`.\n *\n * Applications that never call `vault.closePeriod()` /\n * `vault.openPeriod()` ship none of the ~363 LOC.\n *\n * @internal\n */\n\nimport type { EncryptedEnvelope, NoydbStore } from '../../kernel/types.js'\nimport type { LedgerStore } from '../../with-commit/history/ledger/store.js'\nimport type { PeriodRecord, PeriodPartition, PartitionResolver } from './periods.js'\n\n/**\n * @internal\n */\nexport interface PeriodsStrategy {\n loadPeriods(\n adapter: NoydbStore,\n vault: string,\n decrypt: (id: string, envelope: EncryptedEnvelope) => Promise<PeriodRecord>,\n ): Promise<PeriodRecord[]>\n chainAnchor(records: readonly PeriodRecord[], partition?: PeriodPartition): Promise<{\n priorPeriodName?: string\n priorPeriodHash: string\n }>\n assertTsWritable(\n existing: { ts: string | null; record: Record<string, unknown> | null } | null,\n incoming: Record<string, unknown> | null,\n periods: readonly PeriodRecord[],\n scope?: { collection: string; resolve?: PartitionResolver },\n ): void\n validatePeriodName(\n name: string,\n existing: readonly PeriodRecord[],\n partition?: PeriodPartition,\n ): void\n /**\n * Record → timeline resolver built from `withPeriods({ subjects })` (#1005).\n * `undefined` when the caller configured no subjects, which keeps every\n * record on the vault-wide timeline.\n */\n readonly partitionOf?: PartitionResolver\n appendPeriodLedgerEntry(\n ledger: LedgerStore | null,\n actor: string,\n envelope: EncryptedEnvelope,\n periodName: string,\n collection?: string,\n ): Promise<void>\n}\n\n/**\n * No-periods stub. `loadPeriods` returns `[]`; the write-guards do\n * nothing (vaults without closed periods never reject writes);\n * `validatePeriodName` / `appendPeriodLedgerEntry` throw because\n * those paths are only reached when the user explicitly called\n * `closePeriod()` / `openPeriod()` — if they did that without the\n * strategy, they need to wire it.\n *\n * @internal\n */\nconst NOT_ENABLED = new Error(\n 'Accounting periods require the periods strategy. Import ' +\n '`{ withPeriods }` from \"@noy-db/hub/periods\" and pass it to ' +\n '`createNoydb({ periodsStrategy: withPeriods() })`.',\n)\n\nexport const NO_PERIODS: PeriodsStrategy = {\n async loadPeriods() { return [] },\n async chainAnchor() { return { priorPeriodHash: '' } },\n assertTsWritable() {},\n validatePeriodName() { throw NOT_ENABLED },\n async appendPeriodLedgerEntry() { throw NOT_ENABLED },\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/materialized-views/dependency-analyzer.ts","../src/with-formula/materialized-views/query-hash.ts","../src/with-formula/materialized-views/projection-legs.ts","../src/with-formula/materialized-views/registry.ts"],"sourcesContent":["import type { Query, QueryPlan } from '../../kernel/query/builder.js'\nimport type { JoinContext } from '../../kernel/query/join.js'\nimport type { MaterializedViewSpec } from './types.js'\n\n/**\n * Walks a `Query<T>` plan and returns the set of source collection\n * names that any source-write should trigger a refresh on.\n *\n * Handles:\n * - root collection (the one the query was built from)\n * - FK join targets (`.join(field, { as })`)\n *\n * Also handles:\n * - cross-join targets (`.crossJoin(target, { as })`) — v3\n *\n * Deferred:\n * - `.wherePredicate(name)` — v2 predicate primitive\n * - Overlay-name expansion to {base, overlay}\n *\n * The set is materialized at MV registration time. The MV registry\n * uses it to (a) dispatch `onSourceWrite` only to MVs that actually\n * care, and (b) contribute edges to the shared cycle-detection graph.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function analyzeDependencies(query: Query<any>): Set<string> {\n const deps = new Set<string>()\n const plan = query._plan()\n const ctx = query._joinContext()\n\n // The root collection is always a dependency.\n if (ctx?.leftCollection) {\n deps.add(ctx.leftCollection)\n }\n\n // FK join targets contribute additional sources.\n for (const leg of plan.joins) {\n deps.add(leg.target)\n }\n\n // Cross-join targets are also dependency sources — writes to either side\n // must trigger MV refresh. Symmetric with FK-join target handling above.\n for (const clause of plan.clauses) {\n if (clause.type === 'crossJoin') {\n deps.add(clause.target)\n }\n }\n\n // Sub-plans inside OR clauses can carry nested joins. Walk them.\n // (Today only top-level `.join()` populates `plan.joins`, but the\n // OR-group machinery permits sub-plans, so we recurse defensively.)\n walkClausesForJoins(plan, deps, ctx)\n\n return deps\n}\n\nfunction walkClausesForJoins(\n plan: QueryPlan,\n deps: Set<string>,\n ctx: JoinContext | undefined,\n): void {\n void ctx\n // Today `plan.joins` carries all join legs at top level. Sub-plans\n // inside OR groups don't currently support nested joins, so the loop\n // below is a no-op safety net for future builder extensions.\n for (const clause of plan.clauses) {\n if (clause.type === 'group') {\n // Group clauses don't (yet) carry their own joins; this is a\n // forward-compat anchor for when OR-groups support nested\n // sources.\n }\n }\n}\n\n/**\n * Convenience: produce a stable string summary of the query plan\n * suitable for `queryHash` derivation. Captures everything the\n * dependency analyzer reads + the where/orderBy/limit/offset\n * structure that affects materialized rows.\n *\n * `joinContext` is intentionally NOT included — the join-resolution\n * function references would defeat hash determinism. The set of join\n * TARGETS (collection names) IS included via the plan.joins legs.\n */\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport function summarizeQueryPlan(query: Query<any>): string {\n const plan = query._plan()\n const ctx = query._joinContext()\n return JSON.stringify({\n root: ctx?.leftCollection ?? null,\n clauses: plan.clauses.map(c => {\n if (c.type === 'crossJoin') {\n return {\n type: 'crossJoin',\n target: c.target,\n as: c.as,\n // Inline on: callback: use sentinel — drift detection disabled for this MV\n onPredicateName: c.onPredicateName ?? (c.on ? '[inline]' : null),\n maxRows: c.maxRows ?? null,\n // `outer` changes the row set, so it is part of the plan's identity\n // for drift detection, not decoration (#1130).\n outer: c.outer ?? false,\n }\n }\n return c\n }),\n orderBy: plan.orderBy,\n limit: plan.limit ?? null,\n offset: plan.offset,\n joins: plan.joins.map(j => ({ field: j.field, as: j.as, target: j.target, mode: j.mode })),\n })\n}\n\n/**\n * Canonical string description of a UNION MV's plan, used as input to\n * `computeQueryHash`.\n *\n * Asymmetry note:\n * - Arm collection names are NOT sorted. Declaration order is\n * semantically meaningful for the dedup-only UNION path —\n * `materializeUnionResult` iterates `spec.unionSources` in\n * declaration order and keeps the first-seen row per composite key\n * (tie-break precedence). If we sorted arms here, a consumer who\n * reordered `unionSources` to change precedence would compute the\n * same `queryHash`, refresh would be a no-op, and stale MV rows\n * would persist. Hashing in declaration order makes any reorder\n * trigger a refresh.\n * - `groupBy` fields ARE sorted. Multi-key groupBy buckets are\n * commutative (`canonicalGroupKey` produces the same composite key\n * regardless of field order in the input spec).\n * - `aggregate` keys ARE sorted. Reducer-spec keys are independent\n * of each other — order of declaration doesn't change output.\n *\n * Per-arm `map` functions are NOT fingerprinted; consumers must bump\n * the MV's `name` (or rely on application-level cache busting) when\n * `map` semantics change non-equivalently.\n */\nexport function summarizeUnionPlan<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n return `union(${summarizeUnionArms(spec)})${summarizeGroupingTail(spec)}`\n}\n\n/**\n * Canonical string description of a projection MV's plan (#810), used\n * as input to `computeQueryHash`.\n *\n * Leg descriptors ARE sorted (contrast with UNION arm order): every\n * leg attaches under a distinct `as` alias, forward legs are\n * one-to-one attachments and collect legs are independent\n * hash-grouped passes, so declaration order never changes the\n * materialized rows — a pure reorder must NOT force a refresh.\n * `from` (#1140) constrains the legal orderings (a leg must follow the\n * alias it attaches to) without making order meaningful: each descriptor\n * carries its own `@alias`, and aliases are unique, so the sorted set\n * still determines the leg graph exactly.\n *\n * The projection `map` is NOT fingerprinted (identical limitation as\n * the UNION arm `map`); consumers must bump the MV's `name` when\n * `map` semantics change non-equivalently.\n */\nexport function summarizeProjectionPlan<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n const projection = spec.projection!\n const legs = projection.joins\n .map(leg => {\n // #1140 — `from` changes which rows a leg attaches to, so it is part of\n // the plan's identity. Omitting it would let two structurally different\n // projections share a queryHash and silently skip a refresh.\n const at = leg.from !== undefined ? `@${leg.from}` : ''\n return 'collect' in leg\n ? `collect${at}:${leg.collect}.${leg.on}→${leg.as}`\n : `field${at}:${leg.field}→${leg.as}`\n })\n .sort()\n const body = JSON.stringify({ source: projection.source, legs })\n return `projection(${body})${summarizeGroupingTail(spec)}`\n}\n\n/** Arm descriptors for `summarizeUnionPlan` — declaration order preserved (see its doc). */\nfunction summarizeUnionArms<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n return (spec.unionSources ?? [])\n .map(s => {\n // Fold each arm's join legs into the summary so adding or\n // reordering joins (which changes the materialized rows) bumps\n // queryHash. Leg order is declaration-significant (legs chain), so\n // it is NOT sorted — same rationale as arm declaration order.\n const joins = s.join?.length\n ? `[${s.join.map(j => `${j.field}→${j.as}`).join(';')}]`\n : ''\n return `${s.collection}${joins}`\n })\n .join(',')\n}\n\n/**\n * Shared `|groupBy(…)|aggregate(…)|money(…)` tail for the UNION and\n * projection summaries — both feed the same post-map grouping pipeline,\n * so the same spec fields fold into both hashes with the same rules.\n */\nfunction summarizeGroupingTail<T extends Record<string, unknown>>(\n spec: MaterializedViewSpec<T>,\n): string {\n const groupBy: string = Array.isArray(spec.groupBy)\n ? [...spec.groupBy].sort().join(',')\n : typeof spec.groupBy === 'string'\n ? spec.groupBy\n : ''\n const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(',') : ''\n // `moneyFields` changes reducer semantics (float → exact BigInt), so\n // declaring / removing / re-keying it must bump queryHash. Keys are\n // sorted — they're independent of each other (one descriptor per\n // output field), same rationale as aggregate keys.\n const moneyKeys = spec.moneyFields ? Object.keys(spec.moneyFields).sort().join(',') : ''\n return `|groupBy(${groupBy})|aggregate(${aggKeys})|money(${moneyKeys})`\n}\n","/**\n * Deterministic hash of a materialized view strategy's \"shape\": MV\n * name + canonical query-plan summary + sorted dependency-set.\n *\n * Used to detect strategy drift: a row whose `_materializedFrom.queryHash`\n * doesn't match the current strategy is considered stale.\n *\n * Web Crypto SHA-256 — no extra deps. Mirrors the v1\n * `computeStrategyHash` pattern.\n */\nimport { sha256Hex } from '../../kernel/enclave/index.js'\n\nexport async function computeQueryHash(\n mvName: string,\n /**\n * Source-collection set the query depends on. Sorted before\n * canonicalization so set iteration order doesn't affect the hash.\n */\n dependencies: ReadonlySet<string>,\n /**\n * Stringified query-plan summary. The caller produces this from the\n * `Query<T>` builder — concretely: a JSON serialization of clauses +\n * orderBy + limit + offset + joins. Function bodies inside\n * `wherePredicate` are NOT included here (those carry their own\n * `predicateHash` to be folded in by a later sub-issue).\n */\n queryPlanSummary: string,\n): Promise<string> {\n const canonical = JSON.stringify({\n mvName,\n dependencies: [...dependencies].sort(),\n queryPlanSummary,\n })\n const bytes = new TextEncoder().encode(canonical)\n return sha256Hex(bytes)\n}\n\n/**\n * Canonicalize a query plan for hashing. Walks the plan structure\n * with sorted keys so insertion order doesn't perturb the result.\n * Lives here rather than in `query/builder.ts` to keep that module\n * stable across MV-specific evolutions.\n *\n * @internal exported for testing\n */\nexport function canonicalizeQueryPlan(plan: unknown): string {\n return JSON.stringify(plan, (_key, value) => {\n if (value && typeof value === 'object' && !Array.isArray(value)) {\n const sorted: Record<string, unknown> = {}\n for (const k of Object.keys(value as Record<string, unknown>).sort()) {\n sorted[k] = (value as Record<string, unknown>)[k]\n }\n return sorted\n }\n return value\n })\n}\n","/**\n * Projection-leg topology (#810, extended by #1140).\n *\n * A projection MV's legs originally all attached to the PRIMARY row: a forward\n * leg named an FK field on `projection.source`, and a reverse `collect` leg's\n * `on` field had to `ref()` the source itself. Anything two FKs away was\n * therefore inexpressible — `bill → entity → client` has no leg shape, because\n * the bill has no `clientId` and `clients.entityId` refs `entities`, not\n * `bills`.\n *\n * `from` closes that: a leg may attach to a previously-declared ALIAS instead of\n * to the primary row. This module owns the two questions that follow from it —\n * *is this leg graph well-formed?* and *which collection does a leg's rows come\n * from?* — so neither the registry nor the executor has to re-derive them.\n *\n * **Why cycles are impossible by construction, and there is no depth cap.**\n * `from` may only name an alias declared EARLIER in `joins`. A leg can therefore\n * never reach forward, the graph is a forest rooted at the primary row, and its\n * depth is bounded by the number of legs. Validation refuses an unknown or\n * not-yet-declared alias, which is the same check.\n */\n\nimport { MaterializedViewConfigError } from '../../kernel/errors.js'\nimport type { ProjectionJoinLeg, ProjectionSpec } from './types.js'\n\n/** Reverse (one-to-many) leg — discriminated on `collect`, as the type is. */\nexport function isCollectLeg(\n leg: ProjectionJoinLeg,\n): leg is Extract<ProjectionJoinLeg, { collect: string }> {\n return 'collect' in leg\n}\n\n/**\n * Refuse a malformed leg graph where `withMaterializedView()` checks the rest of\n * the leg shape — at spec CONSTRUCTION, not at vault open and not at first\n * materialization. Alias wiring is pure configuration: it needs no refs, no\n * records and no store, so the earliest failure is the best one.\n * (Ref EXISTENCE still resolves at first materialization, unchanged — refs\n * cannot exist until after `openVault()` returns.)\n */\nexport function validateProjectionLegs<T extends Record<string, unknown>>(\n mvName: string,\n projection: ProjectionSpec<T>,\n): void {\n // Non-empty and unique `as` are already refused by `withMaterializedView()`\n // itself, earlier and with its own message — not re-checked here. This\n // function owns the `from` wiring and nothing else.\n const seen = new Map<string, ProjectionJoinLeg>()\n for (const leg of projection.joins) {\n if (leg.from !== undefined) {\n const parent = seen.get(leg.from)\n if (!parent) {\n throw new MaterializedViewConfigError(\n `\"${mvName}\": projection leg \"${leg.as}\" declares from: \"${leg.from}\", which is not an ` +\n `alias declared EARLIER in \\`joins\\`. A leg may only attach to the primary row or to a ` +\n `preceding leg — that ordering rule is what makes a cycle impossible. Known so far: ` +\n `${[...seen.keys()].map(a => `\"${a}\"`).join(', ') || '(none)'}`,\n )\n }\n if (isCollectLeg(parent)) {\n throw new MaterializedViewConfigError(\n `\"${mvName}\": projection leg \"${leg.as}\" declares from: \"${leg.from}\", but \"${leg.from}\" ` +\n `is a collect leg and holds an ARRAY of rows, not one record. A leg can only hang off a ` +\n `forward leg (record | null). Attach it to the primary row instead, or collect the ` +\n `far side directly if its ref() reaches \"${projection.source}\".`,\n )\n }\n }\n seen.set(leg.as, leg)\n }\n}\n\n/**\n * The collection a leg's rows come from — the primary source for a root leg, the\n * `from` leg's own target otherwise.\n *\n * Returns `null` when a forward `ref()` somewhere along the chain is not\n * declared yet, which at registration time is the normal case rather than an\n * error: refs are declared after `openVault()` returns, so the registry retries\n * this on every dispatch until it resolves (`_pendingForwardDeps`).\n *\n * `resolveRef` is passed in rather than imported so this module stays free of\n * vault wiring — the registry hands it the ref registry's `getOutbound`, the\n * executor hands it a join context lookup. Both answer the same question.\n */\nexport type RefLookup = (collection: string, field: string) => { target: string } | null\n\nexport function resolveLegOwner(\n leg: ProjectionJoinLeg,\n joins: ReadonlyArray<ProjectionJoinLeg>,\n source: string,\n resolveRef: RefLookup,\n): string | null {\n if (leg.from === undefined) return source\n const parent = joins.find(l => l.as === leg.from)\n if (!parent) return null // refused by validateProjectionLegs; defensive here\n if (isCollectLeg(parent)) return parent.collect // refused too, same reason\n const parentOwner = resolveLegOwner(parent, joins, source, resolveRef)\n if (parentOwner === null) return null\n return resolveRef(parentOwner, parent.field)?.target ?? null\n}\n\n/**\n * The collection a FORWARD leg resolves INTO — i.e. its dependency. `null` while\n * any ref in the chain (including this leg's own) is still undeclared.\n */\nexport function resolveForwardLegTarget(\n leg: Extract<ProjectionJoinLeg, { field: string }>,\n joins: ReadonlyArray<ProjectionJoinLeg>,\n source: string,\n resolveRef: RefLookup,\n): string | null {\n const owner = resolveLegOwner(leg, joins, source, resolveRef)\n if (owner === null) return null\n return resolveRef(owner, leg.field)?.target ?? null\n}\n","import { MaterializedViewSourceUnknownError, RefNotDeclaredError } from '../../kernel/errors.js'\nimport type { ViaGraph, FieldRef, EdgeKind, Grain } from '../../kernel/via/graph.js'\nimport type { Clause, FieldClause } from '../../kernel/query/predicate.js'\nimport type { DeclaredPredicate } from '../../kernel/query/builder.js'\nimport { analyzeDependencies, summarizeQueryPlan, summarizeUnionPlan, summarizeProjectionPlan } from './dependency-analyzer.js'\nimport { computeQueryHash } from './query-hash.js'\nimport { isCollectLeg, resolveForwardLegTarget } from './projection-legs.js'\nimport type { MaterializedViewSpec, MVQueryContext } from './types.js'\n\n/**\n * Whole-record artifact-grain field marker (#638 Task 2) — MUST match\n * `derivations/registry.ts`'s and `vault.ts`'s overlay-edge marker so\n * cross-registry edges (a derivation feeding an MV, or vice versa) resolve\n * to the SAME graph node. See that file's `WHOLE_RECORD` doc comment.\n */\nconst WHOLE_RECORD = '*'\n\n/**\n * One registered MV strategy alongside its derived metadata. Stored\n * type-erased on `TRow` so the registry can hold heterogeneous MVs.\n */\nexport interface RegisteredMV {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n readonly spec: MaterializedViewSpec<any>\n /** Output collection name (`spec.output?.collection ?? spec.name`). */\n readonly outputCollection: string\n /** Set of source collections; populated at registration via the analyzer. */\n readonly dependencies: ReadonlySet<string>\n /** Canonical `queryHash` — `_materializedFrom.queryHash` for every emitted row. */\n readonly queryHash: string\n /**\n * Top-level FieldClauses on the partition field, captured at\n * registration time. Used by the cycle detector to resolve\n * same-collection-as-source edges via the partition-discriminator\n * check. Empty when `spec.output?.partition` is undefined.\n */\n readonly partitionClauses: readonly FieldClause[]\n /**\n * #638 Task 2 — `'record'` for a row-per-source-row Query<T> or\n * `unionSources` MV, `'aggregate'` for a `.groupBy().aggregate()` MV\n * (the shape that requires explicit `sources`, since the dependency\n * analyzer can't walk an aggregate plan). Feeds `edges()`'s grain.\n */\n readonly grain: Grain\n}\n\n/**\n * Vault-internal registry of MV strategies. Owned by `Vault`; not\n * exported. Parallel to v1's `DerivationRegistry`; the two graphs share\n * a single cycle-detection pass at vault open (see `validate`).\n *\n * @internal\n */\nexport class MaterializedViewRegistry {\n /** Keyed by `spec.name`. */\n private readonly _byName = new Map<string, RegisteredMV>()\n /** Keyed by dependency source-collection → MVs that depend on it. */\n private readonly _bySource = new Map<string, RegisteredMV[]>()\n /**\n * Projection MVs (#810) whose forward-leg ref() targets could not be\n * resolved at registration time. A forward leg names only the FK\n * FIELD; its dependency is the field's ref() TARGET, declared on the\n * source collection — but registration runs at vault open, BEFORE\n * user code declares collections (and their refs). Constructing the\n * source collection here to look them up would be worse: a later\n * `vault.collection(source, { refs })` call only registers refs on a\n * cache miss, so an early construction would silently drop them.\n * Instead each entry carries a non-constructive ref-registry probe;\n * `mvsForSource` retries on every dispatch until every forward\n * target has resolved (see `_resolvePendingForwardDeps`).\n */\n private readonly _pendingForwardDeps: Array<{\n reg: RegisteredMV\n /** Forward-leg ALIASES still awaiting resolution (#1140 — a leg is identified\n * by its alias, not its field: with `from`, two legs may name the same FK\n * field on different collections). */\n aliases: Set<string>\n resolveTarget: (alias: string) => string | null\n }> = []\n\n /**\n * Query-form strategies whose `query()` callback could not be planned at\n * registration time because a `.join()` names a field whose `ref()` is not\n * declared YET (#1139).\n *\n * Registration runs inside `openVault()`; a collection's refs can only be\n * declared afterwards, so for a query-form MV that joins a declared FK there\n * is NO call ordering the consumer can choose — the plan is simply built too\n * early. Rather than failing the vault open, the strategy is parked here and\n * replanned from {@link resolvePendingPlans}, which every write dispatch and\n * `vault.refreshView()` call runs first. This is the query-form counterpart\n * of `_pendingForwardDeps`, one level up: that defers a leg's TARGET, this\n * defers the whole plan.\n *\n * Only `RefNotDeclaredError` parks a strategy. Every other planning failure\n * still throws out of `openVault()` exactly as before, so a typo'd source or\n * a malformed spec keeps its loud, early error.\n *\n * A parked strategy is invisible until it resolves: it contributes no\n * `_bySource` entries and no cycle-detection edges (same caveat as a\n * late-resolved forward target — `edges()` runs once at vault open). It also\n * cannot be dispatched, which is why {@link hasPendingPlans} forces the\n * conservative branch in `Collection._txAtomicSafe`: the atomic write path\n * skips MV dispatch, and skipping it here would mean the write that was\n * supposed to trigger the first replan is the one that gets missed.\n */\n private readonly _pendingPlans: Array<{\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: MaterializedViewSpec<any>\n db: MVQueryContext\n options?: { knownCollections?: (name: string) => boolean }\n }> = []\n\n /**\n * Register an MV. Invokes `spec.query()` once at registration time to\n * read the plan + join context; the resulting `Query<T>` is discarded\n * after dependency extraction. `vault.collection(...)` must therefore\n * be functional by the time this runs — typically wired from\n * `Vault._initMaterializedViews` after collection bootstrap.\n *\n * Throws `MaterializedViewSourceUnknownError` if the analyzer\n * surfaces a dependency the vault doesn't know about (when a\n * `knownCollections` checker is supplied).\n */\n async register(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: MaterializedViewSpec<any>,\n db: MVQueryContext,\n options?: { knownCollections?: (name: string) => boolean },\n ): Promise<void> {\n // Build a predicate-aware db wrapper. If `spec.predicates` is\n // declared, the wrapper intercepts `.collection().query()` and\n // attaches the predicates map to the resulting Query<T>. With no\n // predicates declared, the wrapper is the original db unchanged.\n const dbForQuery = spec.predicates ? wrapDbWithPredicates(db, spec.predicates) : db\n\n // Invoke the query callback once to inspect its plan / dependencies.\n // For Query<T> shapes the analyzer extracts deps + plan summary\n // automatically. Reduction / GroupedReduction shapes don't\n // expose the underlying Query, so the spec must declare `sources`\n // explicitly. `partitionClauses` are only populated for Query<T>\n // since same-collection-partition is a non-aggregate concern.\n // UNION-form strategies: dependencies and plan summary come\n // straight off the strategy — no `query` callback to introspect.\n // The dependency-analyzer + summarizer are bypassed entirely; the\n // executor handles materialization via `materializeUnionResult`.\n let dependencies: Set<string>\n let queryPlanSummary: string\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let qAny: any = null\n let isQuery = false\n // Projection-form (#810): forward legs whose ref() target wasn't\n // resolvable at registration time — folded in lazily, see\n // `_pendingForwardDeps`.\n const pendingForwardFields: string[] = []\n if (spec.unionSources) {\n dependencies = new Set(spec.unionSources.map(s => s.collection))\n // Per-arm joins resolve right-side collections that aren't among\n // the arm `collection`s. The consumer lists those in `sources`;\n // fold them into the dependency set so a write to a join-target\n // collection triggers MV refresh (and contributes a cycle edge).\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n queryPlanSummary = summarizeUnionPlan(spec)\n } else if (spec.projection) {\n // Projection-form (#810): dependencies are all AUTO — the primary\n // source, every collect leg's collection (both literal names), and\n // every forward leg's ref() target. Forward targets usually can't\n // resolve yet (refs are declared after vault open) — those go\n // through the pending-resolution path below. Explicit `sources`\n // remains additive, same as the other two forms.\n const projection = spec.projection\n dependencies = new Set([projection.source])\n for (const leg of projection.joins) {\n // A collect leg's dependency is its literal collection name, whatever it\n // attaches to — which is why `from` needs no new dependency machinery on\n // that side. A forward leg's dependency is a ref() target, still unknown.\n if (isCollectLeg(leg)) dependencies.add(leg.collect)\n else pendingForwardFields.push(leg.as)\n }\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n queryPlanSummary = summarizeProjectionPlan(spec)\n } else {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n let q: any\n try {\n q = spec.query!(dbForQuery)\n } catch (e) {\n // #1139 — a join on a ref that is not declared YET. Park and replan;\n // see `_pendingPlans`. Anything else is a real registration failure.\n if (!(e instanceof RefNotDeclaredError)) throw e\n this._pendingPlans.push({ spec, db, ...(options !== undefined ? { options } : {}) })\n return\n }\n qAny = q // already `any` — see the declaration above\n isQuery = typeof qAny._plan === 'function'\n if (isQuery) {\n // `q` is the `Query` arm of the union here (runtime-confirmed via\n // `qAny._plan` above); reuse the already-`any` `qAny` the block uses\n // for `_plan()` rather than re-narrowing.\n dependencies = analyzeDependencies(qAny)\n queryPlanSummary = summarizeQueryPlan(qAny)\n // Fold `.wherePredicate(name, ctx)` references into the plan\n // summary so predicate function or ctx changes (signalled by\n // bumping `hash` or supplying a different ctx) propagate into\n // `queryHash` and force refresh on next visit.\n const predicateRefs = extractPredicateRefs(qAny._plan())\n if (predicateRefs.length > 0) {\n queryPlanSummary = JSON.stringify({ plan: queryPlanSummary, predicates: predicateRefs })\n }\n // If `sources` is ALSO declared, take the union (consumer's\n // explicit list extends the auto-analyzed set).\n if (spec.sources) for (const s of spec.sources) dependencies.add(s)\n } else {\n // Aggregate shape: require explicit `sources`.\n if (!spec.sources || spec.sources.length === 0) {\n throw new Error(\n `withMaterializedView \"${spec.name}\": query() returned an aggregate ` +\n `(Reduction or GroupedReduction) but no \\`sources\\` field is declared. ` +\n `The dependency analyzer cannot walk through groupBy().aggregate() ` +\n `back to the source — declare sources: [...] explicitly.`,\n )\n }\n dependencies = new Set(spec.sources)\n // Aggregate plans don't carry a chainable query plan for summary\n // purposes; the dep-set + spec.name serve as the queryHash inputs.\n queryPlanSummary = JSON.stringify({ aggregate: true, sources: [...spec.sources].sort() })\n }\n }\n\n // Sanity-check declared dependencies against the vault's known\n // collections. Optional — when the checker isn't supplied (test\n // wiring, in-process composition) the registration succeeds and\n // any typo surfaces at first onSourceWrite as a no-op.\n if (options?.knownCollections) {\n for (const dep of dependencies) {\n if (!options.knownCollections(dep)) {\n throw new MaterializedViewSourceUnknownError(spec.name, dep)\n }\n }\n }\n\n const outputCollection = spec.output?.collection ?? spec.name\n const queryHash = await computeQueryHash(spec.name, dependencies, queryPlanSummary)\n // For same-collection-as-source MVs, capture the where-clauses on\n // the partition field so cycle detection can prove disjointness.\n // Only applicable to Query<T> shapes — aggregate MVs don't carry\n // a chainable plan to inspect (and same-collection aggregation\n // doesn't make sense for same-collection aggregation).\n const partitionClauses: FieldClause[] = []\n const partitionField = spec.output?.partition?.field\n if (partitionField !== undefined && isQuery) {\n const plan = qAny._plan()\n for (const clause of plan.clauses) {\n if (isFieldClauseOnField(clause, partitionField)) partitionClauses.push(clause)\n }\n }\n // #638 Task 2 — 'aggregate' only for the explicit-sources aggregate shape\n // (groupBy().aggregate() with no chainable plan); a row-per-source-row\n // Query<T>, unionSources, or projection MV is 'record'.\n const grain: Grain = spec.unionSources || spec.projection || isQuery ? 'record' : 'aggregate'\n const reg: RegisteredMV = { spec, outputCollection, dependencies, queryHash, partitionClauses, grain }\n\n this._byName.set(spec.name, reg)\n for (const dep of dependencies) {\n const arr = this._bySource.get(dep)\n if (arr) arr.push(reg)\n else this._bySource.set(dep, [reg])\n }\n if (pendingForwardFields.length > 0) {\n // Non-constructive ref-registry probe: the Vault (the real\n // MVQueryContext) carries its RefRegistry as a private field,\n // reachable structurally at runtime. Test stubs without one simply\n // never resolve — forward deps then come only from explicit `sources`.\n const refRegistry = (db as unknown as {\n refRegistry?: { getOutbound(collection: string): Record<string, { target: string }> }\n }).refRegistry\n const projectionSpec = spec.projection!\n const resolveRef = (c: string, f: string): { target: string } | null =>\n refRegistry?.getOutbound(c)[f] ?? null\n this._pendingForwardDeps.push({\n reg,\n aliases: new Set(pendingForwardFields),\n resolveTarget: (alias) => {\n const leg = projectionSpec.joins.find(l => l.as === alias)\n if (!leg || isCollectLeg(leg)) return null\n // Walks the `from` chain — every hop needs its own ref declared, so a\n // two-hop leg simply resolves one dispatch later than a one-hop one.\n return resolveForwardLegTarget(leg, projectionSpec.joins, projectionSpec.source, resolveRef)\n },\n })\n // Immediate attempt — covers refs already declared by the time\n // this MV registers (re-registration flows, test wiring).\n this._resolvePendingForwardDeps()\n }\n }\n\n /**\n * `true` while any query-form strategy is still unplanned (#1139). Read by\n * `Collection._txAtomicSafe`, which must refuse the atomic write path while\n * this holds — see the `_pendingPlans` doc comment for why.\n */\n hasPendingPlans(): boolean {\n return this._pendingPlans.length > 0\n }\n\n /**\n * Replan every parked query-form strategy (#1139). Called at the head of both\n * write dispatchers and of `vault.refreshView()`, so a strategy resolves on the\n * first write or refresh after its source collection's refs are declared —\n * which is the earliest moment it could produce a correct plan, and strictly\n * before the first moment it could have any output to produce.\n *\n * Each entry is handed straight back to {@link register}, so a strategy that\n * is STILL unplannable simply re-parks itself and the next dispatch tries\n * again. Idempotent and cheap: the array empties as strategies resolve, and\n * both callers skip the await entirely once it is empty.\n *\n * A late-resolved strategy does NOT retro-feed `edges()` — the unified cycle\n * pass runs once at vault open, before refs exist. Same caveat, and the same\n * reason, as a late-resolved forward-leg target.\n */\n async resolvePendingPlans(): Promise<void> {\n if (this._pendingPlans.length === 0) return\n // Splice first: `register` re-parks anything still unplannable, and reading\n // a live array it is concurrently pushing to would loop.\n const parked = this._pendingPlans.splice(0)\n for (const entry of parked) {\n await this.register(entry.spec, entry.db, entry.options)\n }\n }\n\n /** All MVs that depend on `source`, in registration order. */\n mvsForSource(source: string): ReadonlyArray<RegisteredMV> {\n if (this._pendingForwardDeps.length > 0) this._resolvePendingForwardDeps()\n return this._bySource.get(source) ?? []\n }\n\n /**\n * Retry ref() resolution for every pending projection forward leg\n * (#810). A field resolves once its source collection's refs have\n * been declared; the target then folds into the owning MV's\n * dependency set and `_bySource`, so subsequent writes to it\n * dispatch a refresh. Idempotent and cheap — the pending list\n * empties as fields resolve, and `mvsForSource` skips the call\n * entirely once it's empty.\n *\n * Late-resolved targets do NOT retro-feed `edges()` — the cycle\n * pass runs once at vault open, before refs exist. A cycle routed\n * exclusively through a forward leg is therefore not detected at\n * open (collect + source edges, the literal names, are).\n */\n private _resolvePendingForwardDeps(): void {\n for (let i = this._pendingForwardDeps.length - 1; i >= 0; i--) {\n const entry = this._pendingForwardDeps[i]!\n for (const alias of [...entry.aliases]) {\n const target = entry.resolveTarget(alias)\n if (target === null) continue\n entry.aliases.delete(alias)\n if (entry.reg.dependencies.has(target)) continue\n // `dependencies` is declared ReadonlySet for consumers; the\n // registry owns the underlying Set and is the one writer.\n ;(entry.reg.dependencies as Set<string>).add(target)\n const arr = this._bySource.get(target)\n if (arr) arr.push(entry.reg)\n else this._bySource.set(target, [entry.reg])\n }\n if (entry.aliases.size === 0) this._pendingForwardDeps.splice(i, 1)\n }\n }\n\n /** Single MV by name, or `undefined`. */\n byName(name: string): RegisteredMV | undefined {\n return this._byName.get(name)\n }\n\n /** Iterate over every registered MV. */\n all(): ReadonlyArray<RegisteredMV> {\n return [...this._byName.values()]\n }\n\n /**\n * Graph edges for #638 Task 2: one `'mv'` edge per registered MV — target\n * = the output collection (a `WHOLE_RECORD` artifact node), sources =\n * every dependency (also `WHOLE_RECORD` nodes). Same-collection edges\n * (`dep === outputCollection`) are skipped IFF the MV declares an\n * `output.partition` discriminator AND the query has a where-clause that\n * provably excludes the partition value (`partitionDisjoint`) — the SAME\n * condition the old local DFS used. Grain comes from the registration-time\n * `RegisteredMV.grain`.\n */\n edges(): ReadonlyArray<{ readonly target: FieldRef; readonly sources: readonly FieldRef[]; readonly kind: EdgeKind; readonly grain: Grain }> {\n const out: Array<{ target: FieldRef; sources: FieldRef[]; kind: EdgeKind; grain: Grain }> = []\n for (const reg of this._byName.values()) {\n const sources: FieldRef[] = []\n for (const dep of reg.dependencies) {\n if (dep === reg.outputCollection && partitionDisjoint(reg)) continue\n sources.push({ collection: dep, field: WHOLE_RECORD })\n }\n if (sources.length === 0) continue\n out.push({ target: { collection: reg.outputCollection, field: WHOLE_RECORD }, sources, kind: 'mv', grain: reg.grain })\n }\n return out\n }\n\n /**\n * Cycle detection, delegated to `ViaGraph.assertAcyclic()` (#638 Task 2 —\n * retires the local DFS). `graph` is the caller's shared per-vault graph,\n * ALREADY carrying this registry's `edges()` AND `DerivationRegistry`'s\n * (registered by the caller — see `Vault._initMaterializedViews`, which\n * runs after `_initDerivations`, so a pure-derivation cycle already threw\n * `DerivationCycleError` there; any cycle surfacing here necessarily\n * touches an MV edge). Throws `MaterializedViewCycleError` — SAME class as\n * before.\n */\n validate(graph: ViaGraph): void {\n graph.assertAcyclic()\n }\n}\n\n/**\n * Type guard: is the clause a top-level `FieldClause` on the given\n * field? Used by the partition-disjoint check.\n *\n * @internal\n */\nfunction isFieldClauseOnField(clause: Clause, field: string): clause is FieldClause {\n return clause.type === 'field' && clause.field === field\n}\n\n/**\n * Wrap an `MVQueryContext` so its `.collection().query()` returns a\n * Query<T> with the MV's declared predicates attached. Bare Queries\n * (outside of any MV) don't gain `.wherePredicate()` — only Queries\n * obtained through this wrapped db do.\n *\n * @internal\n */\nexport function wrapDbWithPredicates(\n db: MVQueryContext,\n predicates: NonNullable<MaterializedViewSpec<Record<string, unknown>>['predicates']>,\n): MVQueryContext {\n // Build the predicate map once — the fn signature in the MV spec\n // is row-typed but the QueryBuilder casts to unknown, so we widen\n // here for the Map.\n const map = new Map<string, DeclaredPredicate>()\n for (const [name, decl] of Object.entries(predicates)) {\n map.set(name, {\n hash: decl.hash,\n fn: decl.fn as (record: unknown, ctx?: unknown) => boolean,\n })\n }\n return {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n collection<T extends Record<string, unknown>>(name: string): any {\n const c = db.collection<T>(name)\n // Return an object that delegates everything to `c` but\n // overrides `.query()` to attach predicates via the new\n // `Query._withPredicates()` accessor.\n return new Proxy(c, {\n get(target, prop, receiver) {\n if (prop === 'query') {\n return (...args: unknown[]) => {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const q = (target.query as any)(...args)\n // For non-aggregate Query<T>, attach predicates. For\n // legacy predicate-arg overload that returns T[] (sync\n // filter), pass through unchanged.\n \n if (q && typeof q._withPredicates === 'function') {\n return q._withPredicates(map)\n }\n return q\n }\n }\n return Reflect.get(target, prop, receiver)\n },\n })\n },\n }\n}\n\n/**\n * Walk a QueryPlan's clauses and collect predicate-reference markers\n * for `queryHash` derivation. Returns a sorted array (deterministic\n * order) of `{ name, predicateHash, ctxHash }` tuples — these are the\n * hashable identity of each `.wherePredicate()` call site.\n *\n * @internal\n */\nfunction extractPredicateRefs(\n plan: { clauses: readonly Clause[] },\n): Array<{ name: string; predicateHash: string; ctxHash: string }> {\n const refs: Array<{ name: string; predicateHash: string; ctxHash: string }> = []\n const walk = (clauses: readonly Clause[]): void => {\n for (const c of clauses) {\n if (c.type === 'wherePredicate') {\n refs.push({ name: c.name, predicateHash: c.predicateHash, ctxHash: c.ctxHash })\n } else if (c.type === 'group') {\n walk(c.clauses)\n }\n }\n }\n walk(plan.clauses)\n // Stable-sort by (name, predicateHash, ctxHash) — same predicate\n // appearing twice with different ctx hashes both flow through.\n refs.sort((a, b) => {\n if (a.name !== b.name) return a.name < b.name ? -1 : 1\n if (a.predicateHash !== b.predicateHash) return a.predicateHash < b.predicateHash ? -1 : 1\n return a.ctxHash < b.ctxHash ? -1 : a.ctxHash > b.ctxHash ? 1 : 0\n })\n return refs\n}\n\n/**\n * Provability check for the same-collection partition-discriminator.\n * Returns `true` when\n * the captured partition clauses on the MV's query provably exclude\n * the partition's value — meaning the input filter and the output\n * partition are disjoint and the same-collection edge isn't really a\n * cycle.\n *\n * Supported provability shapes (narrow on purpose — DERIV-PP30-001\n * is the load-bearing case):\n *\n * - `.where(field, '==', X)` where X !== partition.value → disjoint\n * - `.where(field, '!=', partition.value)` → disjoint\n * - `.where(field, 'in', [...])` where partition.value NOT in list → disjoint\n *\n * Anything else (no clause on the partition field, an 'in' list that\n * contains partition.value, unsupported operators) → not disjoint,\n * the cycle detector surfaces `MaterializedViewCycleError`.\n *\n * @internal\n */\nfunction partitionDisjoint(reg: RegisteredMV): boolean {\n const partition = reg.spec.output?.partition\n if (partition === undefined) return false\n const value = partition.value\n // The OR-semantics of multiple where-clauses on the same field\n // would muddy this check. v2 only treats AND-chained clauses;\n // any clause that proves disjoint is sufficient.\n for (const c of reg.partitionClauses) {\n if (c.op === '==' && c.value !== value) return true\n if (c.op === '!=' && c.value === value) return true\n if (c.op === 'in' && Array.isArray(c.value)) {\n const list = c.value as readonly unknown[]\n if (!list.includes(value)) return true\n }\n }\n return false\n}\n"],"mappings":"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","names":[]}