@noy-db/hub 0.4.0-pre.1 → 0.4.0-pre.11

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 (605) hide show
  1. package/README.md +3 -4
  2. package/dist/api-ZDKTWNBB.js +20 -0
  3. package/dist/attestation/index.js +18 -18
  4. package/dist/{backup-LU2DTZTI.js → backup-2UX7PRM5.js} +13 -13
  5. package/dist/blobs/index.js +30 -15
  6. package/dist/blobs/index.js.map +1 -1
  7. package/dist/broker/index.js +12 -12
  8. package/dist/broker/index.js.map +1 -1
  9. package/dist/cargo/index.js +53 -43
  10. package/dist/{chunk-YTGDPCPW.js → chunk-2J6UEN2V.js} +42 -18
  11. package/dist/chunk-2J6UEN2V.js.map +1 -0
  12. package/dist/{chunk-IY23KFNZ.js → chunk-35NMVJ7I.js} +3 -3
  13. package/dist/{chunk-S3BFKGET.js → chunk-3HUYRN24.js} +4 -4
  14. package/dist/chunk-3HUYRN24.js.map +1 -0
  15. package/dist/{walk-XS3PKM6I.js → chunk-3V3XR4S4.js} +4 -13
  16. package/dist/{walk-XS3PKM6I.js.map → chunk-3V3XR4S4.js.map} +1 -1
  17. package/dist/{chunk-RTEK4C4I.js → chunk-4JQK3L4V.js} +2 -2
  18. package/dist/{chunk-3ELCWU56.js → chunk-4ZKNC6A6.js} +2 -2
  19. package/dist/{chunk-VQNJ7UZL.js → chunk-55LKVALY.js} +106 -10
  20. package/dist/chunk-55LKVALY.js.map +1 -0
  21. package/dist/{chunk-VAIS3SOM.js → chunk-55LX6FSX.js} +2 -2
  22. package/dist/chunk-5D2ALSM5.js +19 -0
  23. package/dist/chunk-5D2ALSM5.js.map +1 -0
  24. package/dist/{chunk-6HPG2YNX.js → chunk-5DRROK5W.js} +2 -2
  25. package/dist/{chunk-S6Q37VGE.js → chunk-5GQEU4DN.js} +2 -2
  26. package/dist/chunk-5JOFWL4G.js +25 -0
  27. package/dist/chunk-5JOFWL4G.js.map +1 -0
  28. package/dist/{chunk-TOPYT5U5.js → chunk-63FE525P.js} +6 -6
  29. package/dist/chunk-6LJY7X5A.js +18 -0
  30. package/dist/chunk-6LJY7X5A.js.map +1 -0
  31. package/dist/{chunk-JVBO4SZL.js → chunk-6UBZ6I5Z.js} +2 -2
  32. package/dist/{chunk-BOKIKJJB.js → chunk-6ZNSB3H6.js} +5 -17
  33. package/dist/chunk-6ZNSB3H6.js.map +1 -0
  34. package/dist/{chunk-BMQWTQZ4.js → chunk-7YVBQJ7K.js} +13 -355
  35. package/dist/chunk-7YVBQJ7K.js.map +1 -0
  36. package/dist/{chunk-UFEVO57O.js → chunk-ABS7FMMD.js} +6 -6
  37. package/dist/chunk-AFHQATY4.js +47 -0
  38. package/dist/chunk-AFHQATY4.js.map +1 -0
  39. package/dist/{chunk-HOONYZY2.js → chunk-AKKRXT23.js} +7 -7
  40. package/dist/chunk-AKKRXT23.js.map +1 -0
  41. package/dist/{chunk-DDHM2TLX.js → chunk-AT2JJEKJ.js} +4 -4
  42. package/dist/chunk-AT2JJEKJ.js.map +1 -0
  43. package/dist/{chunk-M75ZZHSR.js → chunk-BLBAQRBM.js} +2 -2
  44. package/dist/{chunk-3SV2EPMN.js → chunk-BLCWSJLN.js} +88 -81
  45. package/dist/chunk-BLCWSJLN.js.map +1 -0
  46. package/dist/chunk-CADUYBQD.js +151 -0
  47. package/dist/chunk-CADUYBQD.js.map +1 -0
  48. package/dist/{chunk-G6EQYAL6.js → chunk-CI32CAFS.js} +5 -5
  49. package/dist/{chunk-FH52K7EG.js → chunk-CUZLNGD5.js} +2 -6
  50. package/dist/{chunk-FH52K7EG.js.map → chunk-CUZLNGD5.js.map} +1 -1
  51. package/dist/{chunk-2P7EGNKF.js → chunk-CXQW2NWO.js} +145 -3
  52. package/dist/chunk-CXQW2NWO.js.map +1 -0
  53. package/dist/{chunk-6B4DYGWO.js → chunk-DABACXEF.js} +3 -3
  54. package/dist/{chunk-F6T6L2UK.js → chunk-DMH3VZU5.js} +4 -4
  55. package/dist/chunk-DSDXAKIP.js +50 -0
  56. package/dist/chunk-DSDXAKIP.js.map +1 -0
  57. package/dist/{chunk-MQ6WOPSY.js → chunk-DZ2JXHWK.js} +5 -5
  58. package/dist/chunk-DZFQVICS.js +23 -0
  59. package/dist/chunk-DZFQVICS.js.map +1 -0
  60. package/dist/chunk-E23UGFVC.js +63 -0
  61. package/dist/chunk-E23UGFVC.js.map +1 -0
  62. package/dist/{chunk-UBNCR3YK.js → chunk-EE7UHLKH.js} +4 -4
  63. package/dist/chunk-EIPVYUEP.js +654 -0
  64. package/dist/chunk-EIPVYUEP.js.map +1 -0
  65. package/dist/{chunk-EGCRBCNA.js → chunk-EKRSCRNM.js} +3 -3
  66. package/dist/{chunk-YMUGBZN2.js → chunk-FQ5CHJR7.js} +1 -1
  67. package/dist/chunk-FQ5CHJR7.js.map +1 -0
  68. package/dist/{chunk-LB6OH6K2.js → chunk-FQZ6XTQ7.js} +17 -20
  69. package/dist/chunk-FQZ6XTQ7.js.map +1 -0
  70. package/dist/{chunk-IRQY7IR5.js → chunk-FRMPM7WG.js} +4 -4
  71. package/dist/{chunk-DVUZMT2W.js → chunk-G2JUIDFA.js} +9 -9
  72. package/dist/{chunk-DVUZMT2W.js.map → chunk-G2JUIDFA.js.map} +1 -1
  73. package/dist/{chunk-RND5ZPNP.js → chunk-G6WWZNJP.js} +13 -13
  74. package/dist/chunk-G6WWZNJP.js.map +1 -0
  75. package/dist/chunk-GJW4HX2O.js +1 -0
  76. package/dist/{chunk-T6GU5PPZ.js → chunk-GU3IP74F.js} +6 -6
  77. package/dist/{chunk-5OI6BR4H.js → chunk-GWWOGNBK.js} +308 -18
  78. package/dist/chunk-GWWOGNBK.js.map +1 -0
  79. package/dist/{chunk-UNCOC7QH.js → chunk-H2GPHUZD.js} +12 -13
  80. package/dist/chunk-H2GPHUZD.js.map +1 -0
  81. package/dist/{chunk-7CI6CP7Z.js → chunk-HMU5P6RL.js} +5 -5
  82. package/dist/chunk-HMU5P6RL.js.map +1 -0
  83. package/dist/{chunk-YJV2UZ7H.js → chunk-I5GEJX3R.js} +5 -5
  84. package/dist/chunk-I5GEJX3R.js.map +1 -0
  85. package/dist/{chunk-GWKAVKHI.js → chunk-IG6KUWBX.js} +2 -2
  86. package/dist/chunk-IG6KUWBX.js.map +1 -0
  87. package/dist/{chunk-MTQPRQHB.js → chunk-IPUG56GJ.js} +15 -5
  88. package/dist/{chunk-MTQPRQHB.js.map → chunk-IPUG56GJ.js.map} +1 -1
  89. package/dist/{chunk-SMIRGMS7.js → chunk-IXNCZTWA.js} +2 -2
  90. package/dist/{chunk-RM2XV574.js → chunk-IXTSNGYY.js} +3 -3
  91. package/dist/{chunk-KUPKLSX5.js → chunk-IYZISPGD.js} +7 -5
  92. package/dist/chunk-IYZISPGD.js.map +1 -0
  93. package/dist/{chunk-SMGEYW6G.js → chunk-JDOMW53O.js} +26 -16
  94. package/dist/chunk-JDOMW53O.js.map +1 -0
  95. package/dist/{chunk-PK6QCH2C.js → chunk-JJZGIE4U.js} +6 -5
  96. package/dist/{chunk-DRWBEDYO.js → chunk-JYJFGD2H.js} +2 -2
  97. package/dist/{chunk-TAGR72IR.js → chunk-KRCBZYTZ.js} +26 -177
  98. package/dist/chunk-KRCBZYTZ.js.map +1 -0
  99. package/dist/{chunk-HPUWOVR7.js → chunk-L2DGWBCT.js} +14 -14
  100. package/dist/chunk-L2DGWBCT.js.map +1 -0
  101. package/dist/{chunk-VEALDPHW.js → chunk-L3YYLT4E.js} +15 -2
  102. package/dist/chunk-L3YYLT4E.js.map +1 -0
  103. package/dist/{chunk-A7JJLVGH.js → chunk-LNPUSMZL.js} +29 -31
  104. package/dist/chunk-LNPUSMZL.js.map +1 -0
  105. package/dist/{chunk-KUIB4B5Q.js → chunk-LNZKOUJR.js} +2 -2
  106. package/dist/{chunk-IM3QVF53.js → chunk-MBKVNOJS.js} +6 -6
  107. package/dist/chunk-MCHBCNCE.js +367 -0
  108. package/dist/chunk-MCHBCNCE.js.map +1 -0
  109. package/dist/{chunk-VCBHAE4R.js → chunk-MHMLXJ3I.js} +2 -2
  110. package/dist/chunk-MHMLXJ3I.js.map +1 -0
  111. package/dist/{chunk-WNGGJWF7.js → chunk-MSMJJB4J.js} +2 -2
  112. package/dist/chunk-MSMJJB4J.js.map +1 -0
  113. package/dist/{chunk-T36RFODW.js → chunk-MXUT7FTE.js} +2 -2
  114. package/dist/chunk-MXUT7FTE.js.map +1 -0
  115. package/dist/{chunk-BGK233EA.js → chunk-N4BBNYIB.js} +11 -9
  116. package/dist/chunk-N4BBNYIB.js.map +1 -0
  117. package/dist/{chunk-3PF6CRDT.js → chunk-NFIBXF4V.js} +1082 -1583
  118. package/dist/chunk-NFIBXF4V.js.map +1 -0
  119. package/dist/{chunk-LOF2W3JU.js → chunk-NFNJLRY7.js} +368 -52
  120. package/dist/chunk-NFNJLRY7.js.map +1 -0
  121. package/dist/{chunk-N6WF5PKC.js → chunk-NFZGZ4AK.js} +2 -2
  122. package/dist/{chunk-JRBNCUNT.js → chunk-NGSJZFGL.js} +21 -4
  123. package/dist/chunk-NGSJZFGL.js.map +1 -0
  124. package/dist/chunk-NJ4DYYO5.js +81 -0
  125. package/dist/chunk-NJ4DYYO5.js.map +1 -0
  126. package/dist/{chunk-2IALOXZS.js → chunk-NM5UYF6Q.js} +2 -2
  127. package/dist/chunk-NM5UYF6Q.js.map +1 -0
  128. package/dist/{chunk-JBBSYJHP.js → chunk-NYEFP7DY.js} +4 -4
  129. package/dist/chunk-NYEFP7DY.js.map +1 -0
  130. package/dist/{chunk-CCOP5XDW.js → chunk-NZ24MX4J.js} +8 -46
  131. package/dist/chunk-NZ24MX4J.js.map +1 -0
  132. package/dist/{chunk-KWVJAOIN.js → chunk-OLEPKFTW.js} +4 -4
  133. package/dist/chunk-OLEPKFTW.js.map +1 -0
  134. package/dist/{chunk-OF75ACKS.js → chunk-PEATRIYJ.js} +9 -9
  135. package/dist/chunk-PEOZ34EM.js +13 -0
  136. package/dist/chunk-PEOZ34EM.js.map +1 -0
  137. package/dist/{chunk-UC2ETWF3.js → chunk-PTHKZWEH.js} +2 -14
  138. package/dist/{chunk-UC2ETWF3.js.map → chunk-PTHKZWEH.js.map} +1 -1
  139. package/dist/chunk-PVEWA2DU.js +23 -0
  140. package/dist/chunk-PVEWA2DU.js.map +1 -0
  141. package/dist/{chunk-LSASLXGC.js → chunk-QBAC3GXA.js} +1 -1
  142. package/dist/chunk-QBAC3GXA.js.map +1 -0
  143. package/dist/{chunk-RDXW3OBQ.js → chunk-QD46HPZG.js} +2 -2
  144. package/dist/chunk-QD46HPZG.js.map +1 -0
  145. package/dist/{chunk-N55W7KUL.js → chunk-QDIH2744.js} +2 -2
  146. package/dist/{chunk-6HY2X62D.js → chunk-QJNCB5SK.js} +2 -2
  147. package/dist/{chunk-4BRCIYFG.js → chunk-QX76XVQH.js} +6 -6
  148. package/dist/chunk-QX76XVQH.js.map +1 -0
  149. package/dist/{chunk-W2OG6JOL.js → chunk-QZWTRENR.js} +24 -7
  150. package/dist/chunk-QZWTRENR.js.map +1 -0
  151. package/dist/{chunk-6NQMXTYC.js → chunk-RO5NM6UI.js} +2 -2
  152. package/dist/chunk-RO5NM6UI.js.map +1 -0
  153. package/dist/chunk-SGDPRCC4.js +1075 -0
  154. package/dist/chunk-SGDPRCC4.js.map +1 -0
  155. package/dist/{chunk-2PCAVW2I.js → chunk-SHEEBRZ4.js} +2 -2
  156. package/dist/chunk-SMY5X46Z.js +14 -0
  157. package/dist/chunk-SMY5X46Z.js.map +1 -0
  158. package/dist/{chunk-ISMV2QVG.js → chunk-TG634KCO.js} +11 -2
  159. package/dist/chunk-TG634KCO.js.map +1 -0
  160. package/dist/{chunk-J6Q7UVQE.js → chunk-TUXQSFHZ.js} +4 -16
  161. package/dist/{chunk-J6Q7UVQE.js.map → chunk-TUXQSFHZ.js.map} +1 -1
  162. package/dist/{chunk-GULTLPFB.js → chunk-UADPR6F4.js} +3 -3
  163. package/dist/{chunk-GULTLPFB.js.map → chunk-UADPR6F4.js.map} +1 -1
  164. package/dist/{chunk-ZEQWCBCM.js → chunk-UDHBXYGG.js} +27 -5
  165. package/dist/chunk-UDHBXYGG.js.map +1 -0
  166. package/dist/{chunk-RA5VTXXG.js → chunk-V6UQZA2H.js} +2 -2
  167. package/dist/chunk-VG2ZSFYD.js +32 -0
  168. package/dist/chunk-VG2ZSFYD.js.map +1 -0
  169. package/dist/chunk-VKQCVU2Z.js +21 -0
  170. package/dist/chunk-VKQCVU2Z.js.map +1 -0
  171. package/dist/{chunk-QDPZWTN2.js → chunk-W3G5REIU.js} +2 -2
  172. package/dist/{chunk-WEYZTLXN.js → chunk-W3L64Y3J.js} +2 -2
  173. package/dist/{chunk-ONTBCHCP.js → chunk-W66AM5IB.js} +2 -2
  174. package/dist/chunk-W66AM5IB.js.map +1 -0
  175. package/dist/{chunk-A5BDDEXK.js → chunk-WBKZUO5V.js} +26 -43
  176. package/dist/chunk-WBKZUO5V.js.map +1 -0
  177. package/dist/{chunk-CBSY2N76.js → chunk-WLXMTYGO.js} +2 -2
  178. package/dist/{chunk-3U3FBR7O.js → chunk-X3F5YJG2.js} +2 -2
  179. package/dist/{chunk-GQNGCEP5.js → chunk-X45JYIAY.js} +3 -3
  180. package/dist/chunk-X45JYIAY.js.map +1 -0
  181. package/dist/{chunk-6XE5TMPR.js → chunk-X4MVWPHV.js} +3 -3
  182. package/dist/chunk-X4MVWPHV.js.map +1 -0
  183. package/dist/chunk-XZHEOSQ2.js +16 -0
  184. package/dist/chunk-XZHEOSQ2.js.map +1 -0
  185. package/dist/chunk-Y2NBBT6K.js +18 -0
  186. package/dist/chunk-Y2NBBT6K.js.map +1 -0
  187. package/dist/{chunk-DDATL6PU.js → chunk-Y2NJBNX3.js} +8 -8
  188. package/dist/{chunk-DDATL6PU.js.map → chunk-Y2NJBNX3.js.map} +1 -1
  189. package/dist/chunk-YMUFFJCO.js +23 -0
  190. package/dist/chunk-YMUFFJCO.js.map +1 -0
  191. package/dist/{chunk-O7WJ47EF.js → chunk-YWWQM7KP.js} +5 -14
  192. package/dist/chunk-YWWQM7KP.js.map +1 -0
  193. package/dist/{chunk-7O3AYBC7.js → chunk-YZJSJOUI.js} +235 -65
  194. package/dist/chunk-YZJSJOUI.js.map +1 -0
  195. package/dist/{chunk-5WZXVULX.js → chunk-ZTLOXFPB.js} +2 -2
  196. package/dist/chunk-ZTLOXFPB.js.map +1 -0
  197. package/dist/classified/index.js +5 -5
  198. package/dist/{classified-marker-WQVIJAW2.js → classified-marker-7PKJBFCG.js} +3 -3
  199. package/dist/collection-facade-6I5K5IOD.js +45 -0
  200. package/dist/{computed-DSMPXG2Y.js → computed-AB45PS4V.js} +3 -3
  201. package/dist/consent/index.js +13 -9
  202. package/dist/consent/index.js.map +1 -1
  203. package/dist/cover/index.js +37 -0
  204. package/dist/crdt/index.js +4 -0
  205. package/dist/crdt/index.js.map +1 -1
  206. package/dist/custody/index.js +26 -0
  207. package/dist/{dead-filter-2A7IAJ3D.js → dead-filter-FL3WIUZS.js} +2 -2
  208. package/dist/{chunk-GYYCX5K6.js → delegation-QXZ4MPPH.js} +12 -6
  209. package/dist/{chunk-GYYCX5K6.js.map → delegation-QXZ4MPPH.js.map} +1 -1
  210. package/dist/derivations/index.js +12 -12
  211. package/dist/derive-VGS7FKE3.js +22 -0
  212. package/dist/directory/index.js +46 -0
  213. package/dist/dispatch-EGGC7UD7.js +72 -0
  214. package/dist/dispatch-EGGC7UD7.js.map +1 -0
  215. package/dist/dispatch-GDSTUTVP.js +194 -0
  216. package/dist/dispatch-GDSTUTVP.js.map +1 -0
  217. package/dist/{enclave-7MW6ZGXW.js → enclave-SPEEDYZ4.js} +11 -11
  218. package/dist/executor-IEBLTBCW.js +9 -0
  219. package/dist/executor-OTF556PL.js +28 -0
  220. package/dist/executor-RM7XWQDU.js +9 -0
  221. package/dist/export-accessible-P42BQ4PB.js +23 -0
  222. package/dist/extract-partition-TCYXRWBN.js +38 -0
  223. package/dist/{fanout-sidecar-WDYQ77VB.js → fanout-sidecar-SSMRSC2L.js} +9 -9
  224. package/dist/find-OZMTRIK3.js +11 -0
  225. package/dist/forget/index.js +11 -11
  226. package/dist/forget/index.js.map +1 -1
  227. package/dist/guards/index.js +6 -6
  228. package/dist/history/index.js +16 -12
  229. package/dist/history/index.js.map +1 -1
  230. package/dist/i18n/index.js +16 -14
  231. package/dist/i18n/index.js.map +1 -1
  232. package/dist/index.d.ts +42 -42
  233. package/dist/index.js +330 -2106
  234. package/dist/index.js.map +1 -1
  235. package/dist/indexing/index.js +4 -2
  236. package/dist/indexing/index.js.map +1 -1
  237. package/dist/introspection/index.js +28 -0
  238. package/dist/issue-FCKN5O6C.js +19 -0
  239. package/dist/kernel/best-effort-revert.d.ts +9 -1
  240. package/dist/kernel/collection-config.d.ts +12 -70
  241. package/dist/kernel/collection.d.ts +11 -23
  242. package/dist/kernel/enclave/crypto.d.ts +8 -8
  243. package/dist/kernel/enclave/index.d.ts +3 -3
  244. package/dist/kernel/errors.d.ts +41 -22
  245. package/dist/kernel/lazy.d.ts +30 -0
  246. package/dist/kernel/noydb.d.ts +81 -124
  247. package/dist/kernel/query/builder.d.ts +10 -10
  248. package/dist/kernel/query/index.d.ts +0 -6
  249. package/dist/kernel/query/scan-builder.d.ts +4 -4
  250. package/dist/kernel/sync-policy.d.ts +76 -4
  251. package/dist/kernel/types.d.ts +146 -86
  252. package/dist/kernel/validation.d.ts +33 -33
  253. package/dist/kernel/vault.d.ts +41 -229
  254. package/dist/kernel/via/dispatch.d.ts +7 -0
  255. package/dist/{ledger-33BC4TR7.js → ledger-6XBEVWYP.js} +11 -11
  256. package/dist/liberate-CYUOS4NC.js +24 -0
  257. package/dist/{link-set-QK22TDEX.js → link-set-NK6L2A5S.js} +11 -11
  258. package/dist/materialized-views/index.js +22 -19
  259. package/dist/money/index.js +31 -0
  260. package/dist/noydb-FZZIMLCI.js +81 -0
  261. package/dist/overlay-views/index.js +4 -4
  262. package/dist/periods/index.js +15 -13
  263. package/dist/pod/index.js +26 -12
  264. package/dist/{policy-NAHB2TUT.js → policy/index.js} +6 -6
  265. package/dist/port/with/archive-strategy.d.ts +31 -0
  266. package/dist/port/with/blob-strategy.d.ts +15 -5
  267. package/dist/port/with/collection-options.d.ts +171 -0
  268. package/dist/port/with/collection-registries.d.ts +62 -0
  269. package/dist/port/with/strategies.d.ts +134 -0
  270. package/dist/port/with/team-strategy.d.ts +3 -2
  271. package/dist/portability/index.js +8 -8
  272. package/dist/{post-register-JVTMJ6OW.js → post-register-VFIYISUK.js} +6 -6
  273. package/dist/query/index.js +6 -46
  274. package/dist/{aggregate → reduce}/index.js +24 -17
  275. package/dist/reduce/index.js.map +1 -0
  276. package/dist/register-UKU2J6MN.js +25 -0
  277. package/dist/registry-2VCWVHZS.js +19 -0
  278. package/dist/registry-7Y5WF7GS.js +9 -0
  279. package/dist/{registry-VW6P6A3J.js → registry-BWD2ED52.js} +2 -2
  280. package/dist/registry-DTWRJLSF.js +18 -0
  281. package/dist/request-withdrawal-JYDZ5XY2.js +31 -0
  282. package/dist/{reveal-LGBOM2BO.js → reveal-CL2BCW6A.js} +6 -6
  283. package/dist/revoke-2TSAD2T4.js +24 -0
  284. package/dist/satellites/index.js +1 -1
  285. package/dist/schema-update/index.js +26 -0
  286. package/dist/sealed-record/index.js +12 -12
  287. package/dist/search/index.js +36 -0
  288. package/dist/{seed-PWK45HDF.js → seed-NUVJEL7T.js} +12 -12
  289. package/dist/seed-NUVJEL7T.js.map +1 -0
  290. package/dist/sequence/index.js +30 -0
  291. package/dist/session/index.js +13 -9
  292. package/dist/session/index.js.map +1 -1
  293. package/dist/shadow/index.js +6 -2
  294. package/dist/shadow/index.js.map +1 -1
  295. package/dist/signer-K7Z2EOMG.js +25 -0
  296. package/dist/snapshots/index.js +14 -10
  297. package/dist/snapshots/index.js.map +1 -1
  298. package/dist/{stale-FJED42DM.js → stale-A4J26OIC.js} +11 -10
  299. package/dist/storage-YVHZUISN.js +23 -0
  300. package/dist/store/index.js +22 -0
  301. package/dist/{store-coordination-provider-APODTV4W.js → store-coordination-provider-EWRZL7XF.js} +3 -3
  302. package/dist/sync/index.js +15 -12
  303. package/dist/sync/index.js.map +1 -1
  304. package/dist/team/index.js +33 -48
  305. package/dist/tiers/index.js +10 -10
  306. package/dist/to/index.js +1 -1
  307. package/dist/{tx → transactions}/index.js +7 -3
  308. package/dist/transactions/index.js.map +1 -0
  309. package/dist/util/index.js +1 -1
  310. package/dist/{verify-26Y5GNMK.js → verify-7XWHJWUN.js} +5 -5
  311. package/dist/via/blob/active.d.ts +23 -7
  312. package/dist/via/blob/binding.d.ts +1 -1
  313. package/dist/via/blob/index.d.ts +7 -1
  314. package/dist/via/i18n/core.d.ts +1 -1
  315. package/dist/via/i18n/index.d.ts +2 -0
  316. package/dist/via/money/money-reducer.d.ts +2 -2
  317. package/dist/walk-P5D6CF4V.js +21 -0
  318. package/dist/with-audit/consent/index.d.ts +2 -0
  319. package/dist/with-audit/forget/active.d.ts +3 -3
  320. package/dist/with-audit/forget/index.d.ts +6 -5
  321. package/dist/with-audit/forget/strategy.d.ts +3 -3
  322. package/dist/with-audit/guards/executor.d.ts +4 -4
  323. package/dist/with-audit/guards/immutable-guard.d.ts +3 -3
  324. package/dist/with-audit/guards/index.d.ts +1 -1
  325. package/dist/with-audit/guards/registry.d.ts +3 -3
  326. package/dist/with-audit/guards/transition-guard.d.ts +3 -3
  327. package/dist/with-audit/guards/types.d.ts +10 -10
  328. package/dist/with-audit/guards/with-guard.d.ts +2 -2
  329. package/dist/with-audit/periods/index.d.ts +2 -0
  330. package/dist/with-audit/periods/vault-facade.d.ts +6 -0
  331. package/dist/with-audit/portability/export-accessible.d.ts +4 -4
  332. package/dist/with-audit/portability/request-withdrawal.d.ts +3 -3
  333. package/dist/with-audit/portability/withdraw-accessible.d.ts +2 -2
  334. package/dist/with-audit/sealed-record/strategy.d.ts +1 -1
  335. package/dist/with-audit/tiers/index.d.ts +1 -0
  336. package/dist/with-cargo/adopt-partition.d.ts +7 -7
  337. package/dist/{legacy/kernel.d.ts → with-cargo/floor.d.ts} +4 -4
  338. package/dist/with-cargo/index.d.ts +9 -2
  339. package/dist/with-commit/crdt/index.d.ts +2 -0
  340. package/dist/with-commit/history/index.d.ts +2 -0
  341. package/dist/with-commit/history/time-machine.d.ts +2 -2
  342. package/dist/with-commit/numbering/descriptor.d.ts +16 -6
  343. package/dist/with-commit/tx/active.d.ts +5 -5
  344. package/dist/with-commit/tx/index.d.ts +2 -2
  345. package/dist/with-commit/tx/invariants.d.ts +1 -1
  346. package/dist/with-commit/tx/strategy.d.ts +3 -3
  347. package/dist/with-fork/shadow/index.d.ts +2 -0
  348. package/dist/with-fork/snapshots/active.d.ts +2 -2
  349. package/dist/with-fork/snapshots/index.d.ts +3 -1
  350. package/dist/with-fork/snapshots/noydb-facade.d.ts +3 -3
  351. package/dist/with-fork/snapshots/strategy.d.ts +2 -2
  352. package/dist/with-formula/derivations/dispatch.d.ts +98 -0
  353. package/dist/with-formula/derivations/executor.d.ts +2 -2
  354. package/dist/with-formula/derivations/index.d.ts +4 -1
  355. package/dist/with-formula/derivations/registry.d.ts +3 -3
  356. package/dist/with-formula/derivations/stale.d.ts +2 -2
  357. package/dist/with-formula/derivations/types.d.ts +3 -3
  358. package/dist/with-formula/derivations/with-derivation.d.ts +2 -2
  359. package/dist/with-formula/derivations/with-rollup.d.ts +15 -8
  360. package/dist/with-formula/materialized-views/dependency-analyzer.d.ts +3 -3
  361. package/dist/with-formula/materialized-views/dispatch.d.ts +75 -0
  362. package/dist/with-formula/materialized-views/index.d.ts +2 -1
  363. package/dist/with-formula/materialized-views/registry.d.ts +4 -4
  364. package/dist/with-formula/materialized-views/types.d.ts +11 -11
  365. package/dist/with-formula/materialized-views/with-materialized-view.d.ts +2 -2
  366. package/dist/with-formula/overlay-views/index.d.ts +1 -1
  367. package/dist/with-formula/overlay-views/registry.d.ts +4 -4
  368. package/dist/with-formula/overlay-views/types.d.ts +3 -3
  369. package/dist/with-formula/overlay-views/virtual-collection.d.ts +2 -2
  370. package/dist/with-formula/overlay-views/with-overlayed-view.d.ts +2 -2
  371. package/dist/with-lookup/indexing/active.d.ts +5 -5
  372. package/dist/with-lookup/indexing/index.d.ts +3 -1
  373. package/dist/with-lookup/indexing/strategy.d.ts +3 -3
  374. package/dist/with-lookup/{aggregate → reduce}/active.d.ts +8 -8
  375. package/dist/with-lookup/{aggregate → reduce}/groupby.d.ts +21 -21
  376. package/dist/with-lookup/reduce/index.d.ts +34 -0
  377. package/dist/with-lookup/{aggregate → reduce}/reducers.d.ts +11 -12
  378. package/dist/with-lookup/{aggregate/aggregation.d.ts → reduce/reduction.d.ts} +21 -21
  379. package/dist/with-lookup/{aggregate → reduce}/strategy.d.ts +15 -15
  380. package/dist/with-lookup/search/build-docs.d.ts +7 -2
  381. package/dist/with-lookup/search/index.d.ts +1 -1
  382. package/dist/with-party/broker/index.d.ts +1 -0
  383. package/dist/with-party/broker/seed.d.ts +1 -1
  384. package/dist/with-party/custody/liberate.d.ts +3 -3
  385. package/dist/with-party/directory/cover/index.d.ts +1 -5
  386. package/dist/with-party/directory/cover/schema.d.ts +0 -6
  387. package/dist/with-party/directory/cover/storage.d.ts +0 -8
  388. package/dist/with-party/directory/cover/types.d.ts +0 -21
  389. package/dist/with-party/directory/index.d.ts +2 -0
  390. package/dist/with-party/session/dev-unlock.d.ts +2 -2
  391. package/dist/with-party/session/index.d.ts +2 -0
  392. package/dist/with-party/session/session.d.ts +2 -2
  393. package/dist/with-party/team/deed.d.ts +8 -8
  394. package/dist/with-party/team/index.d.ts +4 -7
  395. package/dist/with-party/team/keyring.d.ts +83 -35
  396. package/dist/with-party/team/{managed-passphrase.d.ts → managed-secret.d.ts} +27 -27
  397. package/dist/with-party/team/noydb-facade.d.ts +43 -42
  398. package/dist/with-party/team/peer-recover.d.ts +13 -13
  399. package/dist/with-party/team/recovery.d.ts +5 -5
  400. package/dist/with-party/team/rotate-recover.d.ts +30 -30
  401. package/dist/with-pod/bundle.d.ts +30 -32
  402. package/dist/with-pod/format.d.ts +2 -2
  403. package/dist/with-pod/index.d.ts +2 -1
  404. package/dist/with-shape/blobs/blob-compaction.d.ts +43 -3
  405. package/dist/with-shape/blobs/blob-pinning.d.ts +112 -0
  406. package/dist/with-shape/blobs/blob-set.d.ts +86 -2
  407. package/dist/with-shape/introspection/index.d.ts +6 -0
  408. package/dist/with-shape/schema-update/index.d.ts +1 -0
  409. package/dist/with-store/index.d.ts +3 -2
  410. package/dist/with-store/route-store.d.ts +1 -1
  411. package/dist/{with-party/team/sync-active.d.ts → with-sync/active.d.ts} +1 -1
  412. package/dist/{with-party/team/sync-credentials.d.ts → with-sync/credentials.d.ts} +3 -3
  413. package/dist/{with-party/team/sync.d.ts → with-sync/engine.d.ts} +5 -5
  414. package/dist/{with-party/sync → with-sync}/index.d.ts +4 -2
  415. package/dist/{with-party/team/sync-period-scope.d.ts → with-sync/period-scope.d.ts} +2 -2
  416. package/dist/{with-party/team → with-sync}/presence.d.ts +2 -2
  417. package/dist/{with-party/team/sync-strategy.d.ts → with-sync/strategy.d.ts} +6 -6
  418. package/dist/{with-party/team/sync-transaction.d.ts → with-sync/transaction.d.ts} +3 -3
  419. package/dist/withdraw-accessible-A2PWF5MZ.js +28 -0
  420. package/package.json +45 -36
  421. package/dist/aggregate/index.js.map +0 -1
  422. package/dist/api-IQFZJSQO.js +0 -20
  423. package/dist/as/index.js +0 -30
  424. package/dist/at/index.js +0 -1
  425. package/dist/bundle/index.js +0 -105
  426. package/dist/by/index.js +0 -11
  427. package/dist/chunk-2IALOXZS.js.map +0 -1
  428. package/dist/chunk-2MUZ3LY6.js +0 -205
  429. package/dist/chunk-2MUZ3LY6.js.map +0 -1
  430. package/dist/chunk-2P7EGNKF.js.map +0 -1
  431. package/dist/chunk-3PF6CRDT.js.map +0 -1
  432. package/dist/chunk-3SV2EPMN.js.map +0 -1
  433. package/dist/chunk-4BRCIYFG.js.map +0 -1
  434. package/dist/chunk-5OI6BR4H.js.map +0 -1
  435. package/dist/chunk-5WZXVULX.js.map +0 -1
  436. package/dist/chunk-6NQMXTYC.js.map +0 -1
  437. package/dist/chunk-6XE5TMPR.js.map +0 -1
  438. package/dist/chunk-7CI6CP7Z.js.map +0 -1
  439. package/dist/chunk-7O3AYBC7.js.map +0 -1
  440. package/dist/chunk-A5BDDEXK.js.map +0 -1
  441. package/dist/chunk-A7JJLVGH.js.map +0 -1
  442. package/dist/chunk-BGK233EA.js.map +0 -1
  443. package/dist/chunk-BMQWTQZ4.js.map +0 -1
  444. package/dist/chunk-BOKIKJJB.js.map +0 -1
  445. package/dist/chunk-CCOP5XDW.js.map +0 -1
  446. package/dist/chunk-CHFZDSE4.js +0 -1
  447. package/dist/chunk-DDHM2TLX.js.map +0 -1
  448. package/dist/chunk-GQNGCEP5.js.map +0 -1
  449. package/dist/chunk-GVZQ4NAM.js +0 -14
  450. package/dist/chunk-GVZQ4NAM.js.map +0 -1
  451. package/dist/chunk-GWKAVKHI.js.map +0 -1
  452. package/dist/chunk-HOONYZY2.js.map +0 -1
  453. package/dist/chunk-HPUWOVR7.js.map +0 -1
  454. package/dist/chunk-ISMV2QVG.js.map +0 -1
  455. package/dist/chunk-JBBSYJHP.js.map +0 -1
  456. package/dist/chunk-JRBNCUNT.js.map +0 -1
  457. package/dist/chunk-JZWWLLVY.js +0 -99
  458. package/dist/chunk-JZWWLLVY.js.map +0 -1
  459. package/dist/chunk-KUPKLSX5.js.map +0 -1
  460. package/dist/chunk-KWVJAOIN.js.map +0 -1
  461. package/dist/chunk-LB6OH6K2.js.map +0 -1
  462. package/dist/chunk-LB7FD65R.js +0 -163
  463. package/dist/chunk-LB7FD65R.js.map +0 -1
  464. package/dist/chunk-LOF2W3JU.js.map +0 -1
  465. package/dist/chunk-LSASLXGC.js.map +0 -1
  466. package/dist/chunk-O7WJ47EF.js.map +0 -1
  467. package/dist/chunk-ONTBCHCP.js.map +0 -1
  468. package/dist/chunk-PT37L5DV.js +0 -255
  469. package/dist/chunk-PT37L5DV.js.map +0 -1
  470. package/dist/chunk-RDXW3OBQ.js.map +0 -1
  471. package/dist/chunk-RND5ZPNP.js.map +0 -1
  472. package/dist/chunk-S3BFKGET.js.map +0 -1
  473. package/dist/chunk-SMGEYW6G.js.map +0 -1
  474. package/dist/chunk-T36RFODW.js.map +0 -1
  475. package/dist/chunk-TAGR72IR.js.map +0 -1
  476. package/dist/chunk-UNCOC7QH.js.map +0 -1
  477. package/dist/chunk-VCBHAE4R.js.map +0 -1
  478. package/dist/chunk-VEALDPHW.js.map +0 -1
  479. package/dist/chunk-VQNJ7UZL.js.map +0 -1
  480. package/dist/chunk-W2OG6JOL.js.map +0 -1
  481. package/dist/chunk-WNGGJWF7.js.map +0 -1
  482. package/dist/chunk-YJV2UZ7H.js.map +0 -1
  483. package/dist/chunk-YMUGBZN2.js.map +0 -1
  484. package/dist/chunk-YTGDPCPW.js.map +0 -1
  485. package/dist/chunk-ZEQWCBCM.js.map +0 -1
  486. package/dist/collection-facade-MJXSC3Q6.js +0 -44
  487. package/dist/cover-B5SJDHR3.js +0 -52
  488. package/dist/delegation-A5A75KQO.js +0 -25
  489. package/dist/derive-HJSMH7RA.js +0 -21
  490. package/dist/executor-AXE4XMLT.js +0 -26
  491. package/dist/executor-KGNLJD6M.js +0 -9
  492. package/dist/executor-XGIS6CR7.js +0 -9
  493. package/dist/export-accessible-VVBJM3MM.js +0 -23
  494. package/dist/extract-partition-LGGBTQET.js +0 -38
  495. package/dist/find-YS6XWB46.js +0 -11
  496. package/dist/in/index.js +0 -1
  497. package/dist/issue-LSR5PS6H.js +0 -19
  498. package/dist/legacy/bundle.d.ts +0 -30
  499. package/dist/liberate-GBSAYLPJ.js +0 -24
  500. package/dist/noydb-MTUBMWFE.js +0 -70
  501. package/dist/on/index.js +0 -17
  502. package/dist/port/as/index.d.ts +0 -26
  503. package/dist/port/at/index.d.ts +0 -18
  504. package/dist/port/in/index.d.ts +0 -21
  505. package/dist/port/on/index.d.ts +0 -27
  506. package/dist/port/ui/index.d.ts +0 -21
  507. package/dist/port/with/index.d.ts +0 -18
  508. package/dist/register-X462DNDM.js +0 -23
  509. package/dist/registry-3XMCT7MQ.js +0 -18
  510. package/dist/registry-KHD2I3M2.js +0 -19
  511. package/dist/registry-NPOWJHC4.js +0 -9
  512. package/dist/request-withdrawal-U7K3JTMY.js +0 -31
  513. package/dist/revoke-OCWDSWRQ.js +0 -24
  514. package/dist/seed-PWK45HDF.js.map +0 -1
  515. package/dist/signer-3YFBLZRN.js +0 -25
  516. package/dist/storage-W3GURVFU.js +0 -23
  517. package/dist/tx/index.js.map +0 -1
  518. package/dist/ui/index.js +0 -1
  519. package/dist/with/index.js +0 -31
  520. package/dist/with-lookup/aggregate/index.d.ts +0 -23
  521. package/dist/withdraw-accessible-Y3B2535F.js +0 -28
  522. /package/dist/{api-IQFZJSQO.js.map → api-ZDKTWNBB.js.map} +0 -0
  523. /package/dist/{backup-LU2DTZTI.js.map → backup-2UX7PRM5.js.map} +0 -0
  524. /package/dist/{chunk-IY23KFNZ.js.map → chunk-35NMVJ7I.js.map} +0 -0
  525. /package/dist/{chunk-RTEK4C4I.js.map → chunk-4JQK3L4V.js.map} +0 -0
  526. /package/dist/{chunk-3ELCWU56.js.map → chunk-4ZKNC6A6.js.map} +0 -0
  527. /package/dist/{chunk-VAIS3SOM.js.map → chunk-55LX6FSX.js.map} +0 -0
  528. /package/dist/{chunk-6HPG2YNX.js.map → chunk-5DRROK5W.js.map} +0 -0
  529. /package/dist/{chunk-S6Q37VGE.js.map → chunk-5GQEU4DN.js.map} +0 -0
  530. /package/dist/{chunk-TOPYT5U5.js.map → chunk-63FE525P.js.map} +0 -0
  531. /package/dist/{chunk-JVBO4SZL.js.map → chunk-6UBZ6I5Z.js.map} +0 -0
  532. /package/dist/{chunk-UFEVO57O.js.map → chunk-ABS7FMMD.js.map} +0 -0
  533. /package/dist/{chunk-M75ZZHSR.js.map → chunk-BLBAQRBM.js.map} +0 -0
  534. /package/dist/{chunk-G6EQYAL6.js.map → chunk-CI32CAFS.js.map} +0 -0
  535. /package/dist/{chunk-6B4DYGWO.js.map → chunk-DABACXEF.js.map} +0 -0
  536. /package/dist/{chunk-F6T6L2UK.js.map → chunk-DMH3VZU5.js.map} +0 -0
  537. /package/dist/{chunk-MQ6WOPSY.js.map → chunk-DZ2JXHWK.js.map} +0 -0
  538. /package/dist/{chunk-UBNCR3YK.js.map → chunk-EE7UHLKH.js.map} +0 -0
  539. /package/dist/{chunk-EGCRBCNA.js.map → chunk-EKRSCRNM.js.map} +0 -0
  540. /package/dist/{chunk-IRQY7IR5.js.map → chunk-FRMPM7WG.js.map} +0 -0
  541. /package/dist/{as/index.js.map → chunk-GJW4HX2O.js.map} +0 -0
  542. /package/dist/{chunk-T6GU5PPZ.js.map → chunk-GU3IP74F.js.map} +0 -0
  543. /package/dist/{chunk-SMIRGMS7.js.map → chunk-IXNCZTWA.js.map} +0 -0
  544. /package/dist/{chunk-RM2XV574.js.map → chunk-IXTSNGYY.js.map} +0 -0
  545. /package/dist/{chunk-PK6QCH2C.js.map → chunk-JJZGIE4U.js.map} +0 -0
  546. /package/dist/{chunk-DRWBEDYO.js.map → chunk-JYJFGD2H.js.map} +0 -0
  547. /package/dist/{chunk-KUIB4B5Q.js.map → chunk-LNZKOUJR.js.map} +0 -0
  548. /package/dist/{chunk-IM3QVF53.js.map → chunk-MBKVNOJS.js.map} +0 -0
  549. /package/dist/{chunk-N6WF5PKC.js.map → chunk-NFZGZ4AK.js.map} +0 -0
  550. /package/dist/{chunk-OF75ACKS.js.map → chunk-PEATRIYJ.js.map} +0 -0
  551. /package/dist/{chunk-N55W7KUL.js.map → chunk-QDIH2744.js.map} +0 -0
  552. /package/dist/{chunk-6HY2X62D.js.map → chunk-QJNCB5SK.js.map} +0 -0
  553. /package/dist/{chunk-2PCAVW2I.js.map → chunk-SHEEBRZ4.js.map} +0 -0
  554. /package/dist/{chunk-RA5VTXXG.js.map → chunk-V6UQZA2H.js.map} +0 -0
  555. /package/dist/{chunk-QDPZWTN2.js.map → chunk-W3G5REIU.js.map} +0 -0
  556. /package/dist/{chunk-WEYZTLXN.js.map → chunk-W3L64Y3J.js.map} +0 -0
  557. /package/dist/{chunk-CBSY2N76.js.map → chunk-WLXMTYGO.js.map} +0 -0
  558. /package/dist/{chunk-3U3FBR7O.js.map → chunk-X3F5YJG2.js.map} +0 -0
  559. /package/dist/{classified-marker-WQVIJAW2.js.map → classified-marker-7PKJBFCG.js.map} +0 -0
  560. /package/dist/{at/index.js.map → collection-facade-6I5K5IOD.js.map} +0 -0
  561. /package/dist/{bundle/index.js.map → computed-AB45PS4V.js.map} +0 -0
  562. /package/dist/{by → cover}/index.js.map +0 -0
  563. /package/dist/{in → custody}/index.js.map +0 -0
  564. /package/dist/{dead-filter-2A7IAJ3D.js.map → dead-filter-FL3WIUZS.js.map} +0 -0
  565. /package/dist/{chunk-CHFZDSE4.js.map → derive-VGS7FKE3.js.map} +0 -0
  566. /package/dist/{on → directory}/index.js.map +0 -0
  567. /package/dist/{collection-facade-MJXSC3Q6.js.map → enclave-SPEEDYZ4.js.map} +0 -0
  568. /package/dist/{computed-DSMPXG2Y.js.map → executor-IEBLTBCW.js.map} +0 -0
  569. /package/dist/{cover-B5SJDHR3.js.map → executor-OTF556PL.js.map} +0 -0
  570. /package/dist/{delegation-A5A75KQO.js.map → executor-RM7XWQDU.js.map} +0 -0
  571. /package/dist/{derive-HJSMH7RA.js.map → export-accessible-P42BQ4PB.js.map} +0 -0
  572. /package/dist/{enclave-7MW6ZGXW.js.map → extract-partition-TCYXRWBN.js.map} +0 -0
  573. /package/dist/{fanout-sidecar-WDYQ77VB.js.map → fanout-sidecar-SSMRSC2L.js.map} +0 -0
  574. /package/dist/{executor-AXE4XMLT.js.map → find-OZMTRIK3.js.map} +0 -0
  575. /package/dist/{ui → introspection}/index.js.map +0 -0
  576. /package/dist/{executor-KGNLJD6M.js.map → issue-FCKN5O6C.js.map} +0 -0
  577. /package/dist/{executor-XGIS6CR7.js.map → ledger-6XBEVWYP.js.map} +0 -0
  578. /package/dist/{export-accessible-VVBJM3MM.js.map → liberate-CYUOS4NC.js.map} +0 -0
  579. /package/dist/{extract-partition-LGGBTQET.js.map → link-set-NK6L2A5S.js.map} +0 -0
  580. /package/dist/{with → money}/index.js.map +0 -0
  581. /package/dist/{find-YS6XWB46.js.map → noydb-FZZIMLCI.js.map} +0 -0
  582. /package/dist/{issue-LSR5PS6H.js.map → policy/index.js.map} +0 -0
  583. /package/dist/{post-register-JVTMJ6OW.js.map → post-register-VFIYISUK.js.map} +0 -0
  584. /package/dist/{ledger-33BC4TR7.js.map → register-UKU2J6MN.js.map} +0 -0
  585. /package/dist/{liberate-GBSAYLPJ.js.map → registry-2VCWVHZS.js.map} +0 -0
  586. /package/dist/{link-set-QK22TDEX.js.map → registry-7Y5WF7GS.js.map} +0 -0
  587. /package/dist/{noydb-MTUBMWFE.js.map → registry-BWD2ED52.js.map} +0 -0
  588. /package/dist/{policy-NAHB2TUT.js.map → registry-DTWRJLSF.js.map} +0 -0
  589. /package/dist/{register-X462DNDM.js.map → request-withdrawal-JYDZ5XY2.js.map} +0 -0
  590. /package/dist/{reveal-LGBOM2BO.js.map → reveal-CL2BCW6A.js.map} +0 -0
  591. /package/dist/{registry-3XMCT7MQ.js.map → revoke-2TSAD2T4.js.map} +0 -0
  592. /package/dist/{registry-KHD2I3M2.js.map → schema-update/index.js.map} +0 -0
  593. /package/dist/{registry-NPOWJHC4.js.map → search/index.js.map} +0 -0
  594. /package/dist/{registry-VW6P6A3J.js.map → sequence/index.js.map} +0 -0
  595. /package/dist/{request-withdrawal-U7K3JTMY.js.map → signer-K7Z2EOMG.js.map} +0 -0
  596. /package/dist/{revoke-OCWDSWRQ.js.map → stale-A4J26OIC.js.map} +0 -0
  597. /package/dist/{signer-3YFBLZRN.js.map → storage-YVHZUISN.js.map} +0 -0
  598. /package/dist/{stale-FJED42DM.js.map → store/index.js.map} +0 -0
  599. /package/dist/{store-coordination-provider-APODTV4W.js.map → store-coordination-provider-EWRZL7XF.js.map} +0 -0
  600. /package/dist/{verify-26Y5GNMK.js.map → verify-7XWHJWUN.js.map} +0 -0
  601. /package/dist/{storage-W3GURVFU.js.map → walk-P5D6CF4V.js.map} +0 -0
  602. /package/dist/with-lookup/{aggregate → reduce}/canonical-key.d.ts +0 -0
  603. /package/dist/{with-party → with-sync}/tab-coordination.d.ts +0 -0
  604. /package/dist/{with-party → with-sync}/tab-write-relay.d.ts +0 -0
  605. /package/dist/{withdraw-accessible-Y3B2535F.js.map → withdraw-accessible-A2PWF5MZ.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/derivations/with-derivation.ts","../src/with-formula/derivations/with-rollup.ts"],"sourcesContent":["import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationStrategy, DerivationStrategyHandle } from './types.js'\n\n/**\n * Register a deterministic derivation: one source collection → one or\n * more typed outputs, computed by the user's `derive` function on\n * plaintext after DEK unwrap. Outputs are encrypted with the same DEK\n * as the source and written via the standard `Collection.put` path.\n *\n * See docs/superpowers/specs/2026-05-01-dim14-derivation-v1-design.md.\n */\nexport function withDerivation<\n TSource extends Record<string, unknown>,\n TOutputs extends Record<string, Record<string, unknown> | ReadonlyArray<Record<string, unknown>>>,\n>(spec: DerivationStrategy<TSource, TOutputs>): DerivationStrategyHandle {\n if (!spec.source || spec.source.length === 0) {\n throw new ValidationError('withDerivation: source collection name is required')\n }\n if (!spec.outputs || Object.keys(spec.outputs).length === 0) {\n throw new ValidationError('withDerivation: outputs map must declare at least one output')\n }\n if (spec.deterministic !== true) {\n throw new ValidationError('withDerivation: v1 only supports deterministic derivations')\n }\n if (typeof spec.derive !== 'function') {\n throw new ValidationError('withDerivation: derive must be a function')\n }\n\n // Validate declared sibling sources. Each must be a non-empty\n // string and must differ from the primary source — a self-reference\n // would double-register the strategy under the same `_bySource` key.\n if (spec.sources !== undefined) {\n for (const extra of spec.sources) {\n if (typeof extra !== 'string' || extra.length === 0) {\n throw new ValidationError('withDerivation: each entry in sources[] must be a non-empty string')\n }\n if (extra === spec.source) {\n throw new ValidationError(\n `withDerivation: sources[] must not contain the primary source \"${spec.source}\"`,\n )\n }\n }\n }\n\n // Validate FK triggers. Each `collection` must be a non-empty\n // string differing from the primary source, and `on` a non-empty field.\n if (spec.triggerBy !== undefined) {\n for (const t of spec.triggerBy) {\n if (typeof t?.collection !== 'string' || t.collection.length === 0) {\n throw new ValidationError('withDerivation: each triggerBy entry needs a non-empty `collection`')\n }\n if (t.collection === spec.source) {\n throw new ValidationError(\n `withDerivation: triggerBy.collection must not equal the source \"${spec.source}\" (use sources[] for same-id triggers)`,\n )\n }\n if (typeof t.on !== 'string' || t.on.length === 0) {\n throw new ValidationError(\n `withDerivation: triggerBy on \"${t.collection}\" needs a non-empty \\`on\\` (the FK field on the source)`,\n )\n }\n if (t.maxFanout !== undefined && (!Number.isInteger(t.maxFanout) || t.maxFanout < 1)) {\n throw new ValidationError(\n `withDerivation: triggerBy maxFanout on \"${t.collection}\" must be a positive integer (got ${String(t.maxFanout)}).`,\n )\n }\n }\n }\n\n // Validate array-shape outputs.\n const lifecycleMode = typeof spec.lifecycle === 'string' ? spec.lifecycle : spec.lifecycle.mode\n for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {\n // Self-write output (collection === source): reverse-denorm must declare\n // `denorm` (the fields it owns) — field-level provenance.\n if (outputSpec.shape === 'record' && outputSpec.collection === spec.source) {\n if (!outputSpec.denorm || outputSpec.denorm.length === 0) {\n throw new ValidationError(\n `withDerivation: self-write output \"${outputKey}\" (collection === source \"${spec.source}\") `\n + 'must declare `denorm: [...]` naming the fields it maintains.',\n )\n }\n }\n if (outputSpec.shape === 'array') {\n if (lifecycleMode !== 'eager') {\n throw new ValidationError(\n `withDerivation: shape 'array' supports lifecycle 'eager' only in this release `\n + `Output \"${outputKey}\" declared lifecycle '${lifecycleMode}'. `\n + 'Switch to `lifecycle: \"eager\"` or use shape: \"record\".',\n )\n }\n if (typeof outputSpec.key !== 'function') {\n throw new ValidationError(\n `withDerivation: shape 'array' output \"${outputKey}\" requires \\`key: (out) => string\\`.`,\n )\n }\n if (outputSpec.maxFanout !== undefined) {\n if (!Number.isInteger(outputSpec.maxFanout) || outputSpec.maxFanout < 1) {\n throw new ValidationError(\n `withDerivation: maxFanout for output \"${outputKey}\" must be a positive integer `\n + `(got ${String(outputSpec.maxFanout)}).`,\n )\n }\n }\n }\n }\n\n return {\n __noydb_strategy: 'derivation',\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n spec: spec as DerivationStrategy<any, any>,\n }\n}\n","import { ValidationError } from '../../kernel/errors.js'\nimport type { DerivationStrategy, DerivationStrategyHandle } from './types.js'\n\n/**\n * `withRollup` — aggregate many child records onto a single field of their\n * parent. The reverse of a join: instead of reading children\n * on demand, the parent carries a maintained summary.\n *\n * ```ts\n * withRollup<Sale, Buyer>({\n * from: 'sales', // child collection (the trigger)\n * key: 'buyerId', // FK on the child → parent id\n * into: 'buyers', // parent collection\n * field: 'revenueByYear', // field on the parent to maintain\n * compute: (sales) => groupSumByYear(sales, 'total'),\n * })\n * ```\n *\n * On every write OR delete of a `from` record, the parent at id `child[key]`\n * is recomputed: `compute(allChildren where child[key] === parentId)` is\n * patched onto `parent[field]`. A parent write also recomputes its own\n * aggregate (so a parent created after its children still fills in). Only the\n * `field` is touched — the rest of the parent record is never clobbered — and\n * a value-equality guard suppresses no-op writes. The aggregate is gap-free\n * with respect to child inserts, updates, and deletes.\n *\n * Desugars to a `withDerivation` strategy carrying a `rollup` marker; dispatch\n * handles it without invoking the executor. Eager-only in this slice.\n */\nexport function withRollup<\n TChild extends Record<string, unknown> = Record<string, unknown>,\n TParent extends Record<string, unknown> = Record<string, unknown>,\n>(config: {\n from: string\n key: keyof TChild & string\n into: string\n field: keyof TParent & string\n compute: (children: TChild[]) => unknown\n}): DerivationStrategyHandle {\n const { from, key, into, field, compute } = config\n if (!from || from.length === 0) {\n throw new ValidationError('withRollup: `from` (child collection) is required')\n }\n if (!into || into.length === 0) {\n throw new ValidationError('withRollup: `into` (parent collection) is required')\n }\n if (from === into) {\n throw new ValidationError('withRollup: `from` and `into` must be different collections')\n }\n if (!key || key.length === 0) {\n throw new ValidationError('withRollup: `key` (FK field on the child) is required')\n }\n if (!field || field.length === 0) {\n throw new ValidationError('withRollup: `field` (target field on the parent) is required')\n }\n if (typeof compute !== 'function') {\n throw new ValidationError('withRollup: `compute` must be a function')\n }\n\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const spec: DerivationStrategy<any, any> = {\n source: into, // the parent record is what carries the rolled-up field\n deterministic: true,\n rollup: { from, key, field, compute: compute as (children: unknown[]) => unknown },\n // Synthetic self-write output for registry / cycle bookkeeping. Dispatch\n // patches `field` directly (value-equality guarded); the executor is not run.\n outputs: { value: { shape: 'record', collection: into, denorm: [field] } },\n derive: () => ({ value: {} }), // never invoked for a rollup strategy\n lifecycle: 'eager',\n }\n\n return { __noydb_strategy: 'derivation', spec }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/kernel/best-effort-revert.ts","../src/with-commit/tx/transaction.ts"],"sourcesContent":["/**\n * Best-effort reverse-revert — the shape shared by two independent rollback\n * paths that used to duplicate it: satellite fan-out\n * (`with-shape/satellites/fanout.ts`'s `revertAndCompensate`) and\n * multi-record transactions (`with-commit/tx/transaction.ts`'s\n * `revertExecuted`). Both revert a list of already-executed writes on\n * failure by walking them in REVERSE order and, per leg, restoring the\n * captured prior envelope straight through the raw adapter — `put(prior)`\n * when one existed, `delete` when the leg was a fresh insert — bypassing\n * the Collection layer so the revert itself doesn't re-fire\n * encryption/ledger/change-event machinery. Each leg's raw-adapter revert\n * is best-effort: a throw is swallowed so a revert-path failure never masks\n * the original error that triggered the rollback, and the loop moves on to\n * the next leg.\n *\n * An optional per-leg `compensate` callback runs immediately after a\n * successful raw-adapter revert (still inside the same try, so a throwing\n * callback is swallowed the same way as a throwing raw revert). What the\n * callback does is entirely caller-defined — satellites' fan-out fires\n * `_compensateRevertedWrite` only for legs whose write actually landed\n * (#596); with-commit's transaction executor invalidates the Collection\n * cache. Neither concern belongs here: this helper only knows the generic\n * reverse/best-effort/raw-revert shape.\n *\n * Lives in `kernel/` — not a `with-*` service — so both consumers (one a\n * gated service, `with-commit`; one an always-on schema feature,\n * `with-shape/satellites`) can import it without creating a\n * `with-* → with-*` edge, which would force either family to always bundle\n * the other and defeat the opt-in tree-shaking both are built around.\n *\n * @internal — implementation-sharing only, not part of the public surface.\n */\nimport type { EncryptedEnvelope } from './types.js'\n\n/** One already-executed write a revert pass needs to unwind. */\nexport interface BestEffortRevertLeg {\n readonly vaultName: string\n readonly collectionName: string\n readonly id: string\n /** Envelope captured before the write, or `null` if the id didn't exist yet. */\n readonly prior: EncryptedEnvelope | null\n}\n\n/** The subset of `NoydbStore` a raw-adapter revert needs. */\nexport interface BestEffortRevertAdapter {\n put(vaultName: string, collectionName: string, id: string, envelope: EncryptedEnvelope): Promise<void>\n delete(vaultName: string, collectionName: string, id: string): Promise<void>\n}\n\n/**\n * Revert `executed` in reverse order via the raw adapter, best-effort.\n * `compensate`, when supplied, runs once per leg immediately after that\n * leg's own raw revert succeeds.\n */\nexport async function bestEffortRevert<T extends BestEffortRevertLeg>(\n executed: readonly T[],\n adapter: BestEffortRevertAdapter,\n compensate?: (leg: T) => Promise<void> | void,\n): Promise<void> {\n for (const leg of [...executed].reverse()) {\n try {\n if (leg.prior !== null) {\n await adapter.put(leg.vaultName, leg.collectionName, leg.id, leg.prior)\n } else {\n await adapter.delete(leg.vaultName, leg.collectionName, leg.id)\n }\n if (compensate) await compensate(leg)\n } catch {\n // best-effort — a revert-path failure must not mask the original\n // error that triggered the rollback (matches both callers' prior\n // independent implementations).\n }\n }\n}\n","/**\n * Multi-record atomic transactions.\n *\n * Lets an application stage writes across two or more collections (or\n * vaults) and commit them all-or-nothing.\n *\n * ```ts\n * await db.transaction(async (tx) => {\n * const inv = tx.vault('acme').collection<Invoice>('invoices')\n * const pay = tx.vault('acme').collection<Payment>('payments')\n * await inv.put(invoiceId, { ...invoice, status: 'paid' })\n * await pay.put(paymentId, { invoiceId, amount, paidAt })\n * })\n * // If the body throws before returning: nothing persisted.\n * // If the body returns: all puts committed; any CAS mismatch rolls\n * // the batch back and surfaces as ConflictError.\n * ```\n *\n * ## Atomicity semantics\n *\n * Ops are buffered during the body. On body-return the hub:\n *\n * 1. **Pre-flight** — re-reads every touched envelope and enforces\n * any caller-supplied `expectedVersion`. A mismatch throws\n * `ConflictError` with *no* writes performed.\n * 2. **Execute** — calls `Collection.put()` / `.delete()` for each\n * staged op in declaration order. History snapshots, ledger\n * appends, and change events fire as normal per op.\n * 3. **Unwind on failure** — if step 2 throws mid-batch, each\n * already-committed op is reverted via the raw store (restoring\n * the captured prior envelope, or deleting if none existed). The\n * ledger is NOT rewritten — audit history preserves the partial\n * commit and the revert.\n *\n * **Crash window.** Steps 2–3 are not a storage-layer transaction —\n * if the process dies between two executed ops, the on-disk state is\n * partial. True all-or-nothing atomicity requires a store that\n * implements `NoydbStore.tx()` (DynamoDB `TransactWriteItems`,\n * IndexedDB `readwrite` transaction, …). This executor declares\n * that future integration point via the `tx?()` method + the\n * `StoreCapabilities.txAtomic` bit, but does not yet delegate\n * to it — the cascade into `Fork · Stores` tracks the per-adapter\n * wire-up.\n *\n * ## Not covered\n *\n * - Cross-sync-peer atomicity. Transactions commit against the\n * primary store only; the sync engine pushes on its normal\n * schedule. For cross-peer two-phase commit use `SyncTransaction`\n * via `db.transaction(vaultName)`.\n * - Read-your-writes within the body. `tx.collection().get(id)`\n * returns the most-recently-staged value for that id when one\n * exists; if no staged op has touched the id, it reads the current\n * committed state. Version numbers returned by `get` reflect the\n * pre-transaction state (staged puts have no version yet).\n *\n * @module\n */\n\nimport type { Noydb } from '../../kernel/noydb.js'\nimport type { Vault } from '../../kernel/vault.js'\nimport type { Collection } from '../../kernel/collection.js'\nimport type { EncryptedEnvelope } from '../../kernel/types.js'\nimport {\n AmendmentForbiddenError,\n ConflictError,\n InvariantError,\n ValidationError,\n} from '../../kernel/errors.js'\nimport { generateULID } from '../../with-pod/ulid.js'\nimport type { GuardExecutor as GuardExecutorModule } from '../../with-audit/guards/executor.js'\nimport type { LedgerEntry } from '../history/ledger/entry.js'\nimport type { TransactionInvariant } from './invariants.js'\nimport type { GuardChange, GuardContext, ReadOnlyVaultFacade } from '../../with-audit/guards/types.js'\nimport { bestEffortRevert } from '../../kernel/best-effort-revert.js'\n\n/** One op buffered inside a running `TxContext`. @internal */\nexport interface StagedOp {\n type: 'put' | 'delete'\n vaultName: string\n collectionName: string\n id: string\n record?: unknown\n expectedVersion?: number\n /**\n * Optional human-readable tag forwarded to the resulting ledger\n * entry's `reason` field. Set by callers via\n * `tx.vault(v).collection(c).put(id, record, { reason })`.\n */\n reason?: string\n}\n\n/**\n * One executed op (main staged op or recursive side-effect like a\n * derivation output) paired with the envelope captured before the write.\n * `revertExecuted` walks this array in reverse on rollback.\n * @internal\n */\nexport interface ExecutedOp {\n op: StagedOp\n priorEnvelope: EncryptedEnvelope | null\n}\n\n/**\n * Options accepted by `db.transaction({ amendment, reason }, fn)`.\n * Only the amendment variant uses these — a plain `db.transaction(fn)`\n * never sees this shape.\n */\nexport interface AmendmentTxOptions {\n /** Opt into amendment mode. Required to be `true`. */\n readonly amendment: true\n /** Human-readable rationale recorded in the ledger entry. Required. */\n readonly reason: string\n}\n\n/**\n * Transaction handle passed to the user's body. Use\n * `tx.vault(name).collection<T>(name)` to get a per-collection\n * facade; its `put`/`delete`/`get` calls stage ops against the tx.\n */\nexport class TxContext {\n /** Stable id for this transaction; shared by all writes it performs. */\n readonly txId: string = generateULID()\n /** @internal */\n readonly _ops: StagedOp[] = []\n /**\n * @internal — write log built up in Phase 2. Each entry records the\n * envelope captured BEFORE the write so a mid-batch failure can\n * restore prior state via `revertExecuted`. Side-effect writes (e.g.\n * recursive derivation outputs fired inside `Collection.put`) are\n * appended here in execution order so they roll back alongside the\n * main staged ops.\n */\n readonly _executed: ExecutedOp[] = []\n /** @internal */\n readonly _db: Noydb\n /**\n * @internal — true when this TxContext was opened in amendment\n * mode. Toggles the lazy-`beginAmendment` + role-check path on first\n * `tx.vault(name)` and unlocks the post-Phase-2 invariant + audit run.\n */\n readonly _amendment: boolean\n /** @internal — vaults that have already had `beginAmendment` called. */\n readonly _amendmentVaults = new Map<string, Vault>()\n\n /** @internal */\n constructor(db: Noydb, amendment = false) {\n this._db = db\n this._amendment = amendment\n }\n\n /** Scope subsequent `collection()` calls to the named vault. */\n vault(name: string): TxVault {\n const v = this._db.vault(name)\n if (this._amendment && !this._amendmentVaults.has(name)) {\n // Role check is per-vault. The task spec (\"only admin or owner\n // can open an amendment\") is implemented lazy-on-first-touch\n // because the role lives on the vault's keyring, and `tx.vault()`\n // is the first place we know which vault we're addressing. The\n // observable effect is identical to an eager check in the single-\n // vault case the tests exercise; multi-vault amendments check\n // each touched vault as they first appear.\n const role = v.role\n // FR-6: custodian is admin-rank for operational mutations, and an\n // amendment is an operational (data-correcting) act — not an ownership\n // meta-capability — so custodian is allowed alongside owner/admin.\n if (role !== 'admin' && role !== 'owner' && role !== 'custodian') {\n throw new AmendmentForbiddenError(v.userId, role)\n }\n // Amendments require an initialised guard registry — they\n // produce a structured invariant + change-set audit. A vault\n // opened without `guardStrategies` (or via the sync fallback\n // path) has a null registry and cannot run an amendment.\n const reg = v._getGuardRegistry()\n if (reg === null) {\n throw new ValidationError(\n `Vault \"${name}\": amendment mode requires at least one ` +\n `guardStrategy registered via createNoydb({ guardStrategies }). ` +\n `Open the vault with guardStrategies before calling ` +\n `db.transaction({ amendment: true }).`,\n )\n }\n reg.beginAmendment()\n this._amendmentVaults.set(name, v)\n }\n return new TxVault(this, v)\n }\n}\n\n/** Per-vault facade inside a running transaction. */\nexport class TxVault {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault) {\n this._ctx = ctx\n this._vault = vault\n }\n\n /** Scope subsequent op calls to the named collection. */\n collection<T>(name: string): TxCollection<T> {\n const c = this._vault.collection<T>(name)\n return new TxCollection<T>(this._ctx, this._vault, c, name)\n }\n}\n\n/** Per-collection facade inside a running transaction. */\nexport class TxCollection<T> {\n /** @internal */\n readonly _ctx: TxContext\n /** @internal */\n readonly _vault: Vault\n /** @internal */\n readonly _coll: Collection<T>\n /** @internal */\n readonly _name: string\n\n /** @internal */\n constructor(ctx: TxContext, vault: Vault, coll: Collection<T>, name: string) {\n this._ctx = ctx\n this._vault = vault\n this._coll = coll\n this._name = name\n }\n\n /**\n * Read the current committed value, or the most-recently-staged\n * value from the same transaction if one exists.\n */\n async get(id: string): Promise<T | null> {\n for (let i = this._ctx._ops.length - 1; i >= 0; i--) {\n const op = this._ctx._ops[i]!\n if (\n op.vaultName === this._vault.name &&\n op.collectionName === this._name &&\n op.id === id\n ) {\n if (op.type === 'delete') return null\n return op.record as T\n }\n }\n return this._coll.get(id)\n }\n\n /**\n * Stage a put. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n put(id: string, record: T, options?: { expectedVersion?: number; reason?: string }): void {\n const op: StagedOp = {\n type: 'put',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n record,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n if (options?.reason !== undefined) op.reason = options.reason\n this._ctx._ops.push(op)\n }\n\n /**\n * Stage a delete. Does not write until the transaction body returns.\n * Supply `{ expectedVersion }` to enforce optimistic concurrency\n * during the commit pre-flight.\n */\n delete(id: string, options?: { expectedVersion?: number }): void {\n const op: StagedOp = {\n type: 'delete',\n vaultName: this._vault.name,\n collectionName: this._name,\n id,\n }\n if (options?.expectedVersion !== undefined) op.expectedVersion = options.expectedVersion\n this._ctx._ops.push(op)\n }\n}\n\n/**\n * Commit plan: pre-flight check + execution + revert plan.\n *\n * @internal — driven by `withTransactions()` (via `tx/active.ts`) for\n * user-facing `db.transaction(...)` calls and by the `amendment` path\n * in `noydb.ts`. `Collection.putManyAtomic` runs its own Phase 2 loop\n * but shares the `_activeTxContext` mechanism (and the `revertExecuted`\n * helper) so nested side-effect derivation writes get registered for\n * revert alongside the bulk-put source ops.\n */\nexport async function runTransaction<T>(\n db: Noydb,\n fn: (tx: TxContext) => Promise<T> | T,\n options?: AmendmentTxOptions,\n txInvariants?: ReadonlyArray<TransactionInvariant>,\n): Promise<T> {\n // ─── Amendment-mode pre-flight ───────────────────────────────\n // `reason` is the only thing we can validate before the body runs;\n // the per-vault role check happens lazily on first `tx.vault(name)`\n // because we don't know which vaults the body will touch ahead of\n // time. Throwing here keeps the failure mode close to the call site\n // so the developer doesn't have to walk an async stack to find the\n // missing-reason mistake.\n if (options?.amendment) {\n if (typeof options.reason !== 'string' || options.reason.trim().length === 0) {\n throw new ValidationError(\n 'db.transaction({ amendment: true }) requires a non-empty `reason` string.',\n )\n }\n }\n\n const ctx = new TxContext(db, options?.amendment === true)\n const bodyResult = await fn(ctx)\n\n if (ctx._ops.length === 0) {\n // Body produced no ops. If amendment mode was active we still\n // need to close any opened windows so a subsequent (unrelated)\n // write doesn't surprise-collect into a stale change-set. Each\n // `beginAmendment` is matched by exactly one `consumeChanges`.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n // Registry is guaranteed non-null here — `tx.vault(name)`\n // threw above if it was null before adding to\n // `_amendmentVaults`.\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n return bodyResult\n }\n\n // Phase 1 — pre-flight: snapshot every touched envelope and enforce\n // any caller-supplied expectedVersion. Same (vault, coll, id) touched\n // more than once in one tx snapshots only the *initial* committed\n // state; the in-order replay in Phase 2 takes care of successor ops.\n const priorEnvelopes = new Map<string, EncryptedEnvelope | null>()\n const store = db._store\n\n // Commit-time changeset invariants need PLAINTEXT prior records\n // for `before`, but `priorEnvelopes` holds ENCRYPTED envelopes. So for\n // ops in a watched scope we additionally decrypt the prior record here,\n // in Phase 1, BEFORE Phase 2 overwrites it. Snapshots only the initial\n // committed state per (vault, coll, id), matching the envelope snapshot.\n const invariants = txInvariants ?? []\n const watchedScopes = new Set(invariants.map(i => i.scope))\n const plainBefore = new Map<string, unknown>()\n\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!priorEnvelopes.has(key)) {\n const env = await store.get(op.vaultName, op.collectionName, op.id)\n priorEnvelopes.set(key, env)\n }\n if (watchedScopes.has(op.collectionName) && !plainBefore.has(key)) {\n const prior = await db\n .vault(op.vaultName)\n .collection(op.collectionName)\n .get(op.id)\n plainBefore.set(key, prior ?? null)\n }\n if (op.expectedVersion !== undefined) {\n const env = priorEnvelopes.get(key) ?? null\n const actual = env?._v ?? 0\n if (actual !== op.expectedVersion) {\n throw new ConflictError(\n actual,\n `Transaction pre-flight: ${op.vaultName}/${op.collectionName}/${op.id} ` +\n `expected v${op.expectedVersion}, found v${actual}`,\n )\n }\n }\n }\n\n // Phase 2 — execute via the Collection layer so history snapshots,\n // ledger entries, and change events fire normally. We capture each\n // successful op so a mid-batch throw can revert in Phase 3.\n //\n // `_activeTxContext` is published on the Noydb instance for the\n // duration of Phase 2 so recursive writes triggered inside\n // `Collection.put` (today: eager derivation outputs) can register\n // their own envelopes onto `ctx._executed` and roll back alongside\n // the main staged ops. The `finally` clears it before the\n // amendment commit phase runs.\n db._setActiveTxContext(ctx)\n try {\n try {\n for (const op of ctx._ops) {\n const coll = db.vault(op.vaultName).collection(op.collectionName)\n const key = keyOf(op)\n const prior = priorEnvelopes.get(key) ?? null\n // Record the revert plan BEFORE the call so a mid-`coll.put` throw\n // (e.g. strict-mode derivation failure firing after `store.put`\n // has already committed the envelope) still has its source write\n // reverted. `revertExecuted` is best-effort: putting prior back is\n // idempotent when the failing op never actually wrote, and\n // `_invalidateCacheEntry` is a no-op when the collection isn't\n // hydrated.\n ctx._executed.push({ op, priorEnvelope: prior })\n if (op.type === 'put') {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await coll.put(op.id, op.record as any, op.reason !== undefined ? { reason: op.reason } : undefined)\n } else {\n await coll.delete(op.id)\n }\n }\n } catch (err) {\n // Phase 3 — best-effort revert. See helper docstring.\n await revertExecuted(ctx._executed, store, db)\n // Drain amendment windows so the next transaction starts clean.\n if (ctx._amendment) {\n for (const v of ctx._amendmentVaults.values()) {\n const reg = v._getGuardRegistry()\n if (reg !== null) {\n reg.consumeChanges()\n reg.consumeMeta()\n }\n }\n }\n throw err\n }\n } finally {\n db._clearActiveTxContext(ctx)\n }\n\n // ─── Amendment commit phase (only if amendment === true) ────\n // Body succeeded — now run each touched vault's invariants over the\n // collected change-set, then append a structured ledger entry. If\n // any invariant throws, treat it exactly like a mid-Phase-2 failure:\n // revert every executed op and re-throw the InvariantError.\n if (ctx._amendment) {\n // Lazy-load GuardExecutor at the dispatch site — keeps the floor\n // bundle free of the guards service when amendments aren't used.\n // Mirrors the deferred-load pattern from elsewhere in this module.\n const { GuardExecutor } = (await import('../../with-audit/guards/executor.js')) as {\n GuardExecutor: typeof GuardExecutorModule\n }\n try {\n for (const [vaultName, v] of ctx._amendmentVaults) {\n const registry = v._getGuardRegistry()\n // Registry is guaranteed non-null at this point — the\n // `tx.vault(name)` path that populates `_amendmentVaults`\n // throws if the registry is null. The defensive check here\n // is for TypeScript's narrowing.\n if (registry === null) continue\n const changesByCollection = registry.consumeChanges()\n const meta = registry.consumeMeta()\n if (changesByCollection.size === 0) continue\n\n const readOnlyVault = v._getReadOnlyFacade()\n if (readOnlyVault === null) continue\n\n // Build the invariant ctx once per vault — it's the same shape\n // every guard sees on the normal `check` path, just with a\n // synthetic `existing: null` (invariants get the full change\n // set in their first parameter; `existing` is a per-record\n // concept that doesn't apply here).\n const invariantsPassed: string[] = []\n for (const [collection, changes] of changesByCollection) {\n const guards = registry.guardsFor(collection).filter(g => g.amendment !== undefined)\n for (const guard of guards) {\n await GuardExecutor.runInvariant(guard, changes, {\n existing: null,\n vault: readOnlyVault,\n userId: v.userId,\n role: v.role,\n })\n }\n if (guards.length > 0) invariantsPassed.push(collection)\n }\n\n // Append the audit ledger entry. Silent no-op when the\n // history strategy isn't configured — the records still\n // committed, only the multi-record summary is unavailable.\n const ledger = v._getLedgerOrNull()\n if (ledger) {\n const role = v.role as 'admin' | 'owner'\n const amendment: NonNullable<LedgerEntry['amendment']> = {\n reason: options!.reason,\n role,\n changes: meta,\n invariantsPassed,\n }\n await ledger.append({\n op: 'amendment',\n collection: '',\n id: '',\n version: 0,\n actor: v.userId,\n // No payload to hash — the per-record entries already\n // captured `payloadHash` at their own append time. We use\n // a sha256 of the canonical reason string so the field is\n // populated with something deterministic and non-empty.\n payloadHash: '',\n amendment,\n })\n }\n void vaultName\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n // ─── Commit-time changeset invariant phase ───────────\n // Runs for BOTH ordinary and amendment transactions (placed after the\n // amendment phase so an amendment commit is still subject to these\n // set-level constraints). Assemble the changeset from the executed\n // staged ops, deduped to the LAST write per (vault, coll, id) while\n // preserving write order, then group `GuardChange` by collection\n // (scope) and run each matching invariant. A throw mirrors the\n // amendment-phase failure mode exactly: revert every executed op and\n // re-throw as `InvariantError`.\n if (invariants.length > 0) {\n // Dedup ctx._ops to the last write per key, preserving first-seen\n // (write) order so the changeset is stable and order-meaningful.\n const lastOp = new Map<string, StagedOp>()\n const order: string[] = []\n for (const op of ctx._ops) {\n const key = keyOf(op)\n if (!lastOp.has(key)) order.push(key)\n lastOp.set(key, op)\n }\n\n // Group {before, after} pairs by collection name (the invariant\n // scope). `before` is the plaintext prior captured in Phase 1 (null\n // for inserts / unwatched — only watched scopes were captured, and\n // every grouped key belongs to a watched scope). `after` is the\n // written record, null for a delete.\n const changesByScope = new Map<string, GuardChange<unknown>[]>()\n // Parallel map: scope → the vault name of its (last-seen) op, used to\n // build the per-invariant read-only ctx (facade + userId + role).\n const scopeVault = new Map<string, string>()\n for (const key of order) {\n const op = lastOp.get(key)!\n if (!watchedScopes.has(op.collectionName)) continue\n const before = plainBefore.get(key) ?? null\n const after = op.type === 'delete' ? null : (op.record ?? null)\n const change = { before, after } as GuardChange<unknown>\n const arr = changesByScope.get(op.collectionName)\n if (arr) arr.push(change)\n else changesByScope.set(op.collectionName, [change])\n\n // Stash the vault name alongside so we can build a per-vault ctx.\n // (All ops in a scope group could span vaults; we resolve the\n // vault per change below via a parallel map keyed the same way.)\n scopeVault.set(op.collectionName, op.vaultName)\n }\n\n try {\n for (const inv of invariants) {\n const changes = changesByScope.get(inv.scope)\n if (changes === undefined || changes.length === 0) continue\n const vaultName = scopeVault.get(inv.scope)!\n const v = db.vault(vaultName)\n // Prefer the real read-only facade so the invariant can read\n // sibling collections; fall back to a minimal read-only stub.\n const facade: ReadOnlyVaultFacade =\n v._getReadOnlyFacade() ?? {\n collection<R = unknown>(name: string) {\n const c = v.collection<R>(name)\n return {\n get: (id: string) => c.get(id),\n list: () => c.list(),\n query: () => c.query(),\n }\n },\n }\n const ctxForInv: GuardContext<unknown> = {\n existing: null,\n vault: facade,\n userId: v.userId,\n role: v.role,\n }\n await inv.check(changes, ctxForInv)\n }\n } catch (err) {\n await revertExecuted(ctx._executed, store, db)\n throw err instanceof InvariantError ? err : new InvariantError(\n err instanceof Error ? err.message : `invariant violated: ${String(err)}`,\n )\n }\n }\n\n return bodyResult\n}\n\n/**\n * Phase 3 helper — restore captured prior envelopes via the raw store\n * to avoid re-firing Collection-level side effects (we don't want a\n * cascade of change events undoing themselves). The ledger is left\n * as-is: each committed op appended an entry; the revert is\n * deliberately NOT recorded as a compensating entry because the\n * caller-facing contract is \"atomic or not at all,\" not \"every write\n * visible in the audit trail.\" Auditors who need the intermediate\n * state can still reconstruct it by walking the ledger through the\n * failed-tx timestamp.\n *\n * Delegates the reverse/best-effort/raw-revert shape to the shared\n * `bestEffortRevert` helper (`kernel/best-effort-revert.ts`); the cache\n * invalidation below rides along as that helper's per-leg `compensate`\n * callback, gated on `db` exactly as before.\n *\n * @internal — shared between `runTransaction` and\n * `Collection.putManyAtomic`. Both register source ops + nested\n * derivation side-effect ops onto `_executed`; this helper unwinds the\n * combined list in reverse on rollback.\n */\nexport async function revertExecuted(\n executed: ReadonlyArray<ExecutedOp>,\n store: Noydb['_store'],\n db?: Noydb,\n): Promise<void> {\n const legs = executed.map(({ op, priorEnvelope }) => ({\n vaultName: op.vaultName,\n collectionName: op.collectionName,\n id: op.id,\n prior: priorEnvelope,\n }))\n await bestEffortRevert(\n legs,\n store,\n db\n ? async (leg) => {\n // Sync the Collection-layer cache with what we just wrote at\n // the raw store. Without this, eager-mode `get` would still\n // return the rolled-back record from its in-memory map. The\n // Collection's `_invalidateCacheEntry` is a no-op when the\n // collection hasn't yet been hydrated.\n const coll = db.vault(leg.vaultName).collection(leg.collectionName)\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (coll as any)._invalidateCacheEntry(leg.id)\n }\n : undefined,\n )\n}\n\nfunction keyOf(op: StagedOp): string {\n return `${op.vaultName}\\x00${op.collectionName}\\x00${op.id}`\n}\n"],"mappings":"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","names":[]}
@@ -1,99 +0,0 @@
1
- import {
2
- SYNC_CREDENTIALS_COLLECTION,
3
- ensureCollectionDEK,
4
- grant,
5
- revoke,
6
- rotateKeys
7
- } from "./chunk-3SV2EPMN.js";
8
- import {
9
- openEnvelopeJson
10
- } from "./chunk-DVUZMT2W.js";
11
- import {
12
- encrypt
13
- } from "./chunk-HOONYZY2.js";
14
- import {
15
- NOYDB_FORMAT_VERSION
16
- } from "./chunk-LSASLXGC.js";
17
- import {
18
- PermissionDeniedError
19
- } from "./chunk-W2OG6JOL.js";
20
-
21
- // src/with-party/team/active.ts
22
- function withTeam() {
23
- return {
24
- async grant(team, vault, options, factors) {
25
- return team.runGrant(grant, vault, options, factors);
26
- },
27
- async revoke(team, vault, options, factors) {
28
- return team.runRevoke(revoke, vault, options, factors);
29
- },
30
- async rotate(team, vault, collections) {
31
- return team.runRotate(rotateKeys, vault, collections);
32
- }
33
- };
34
- }
35
-
36
- // src/with-party/team/sync-credentials.ts
37
- function requireAdminAccess(keyring) {
38
- if (keyring.role !== "owner" && keyring.role !== "admin") {
39
- throw new PermissionDeniedError(
40
- `Sync credentials require owner or admin role. Current role: "${keyring.role}"`
41
- );
42
- }
43
- }
44
- async function putCredential(adapter, vault, keyring, credential) {
45
- requireAdminAccess(keyring);
46
- const getDek = await ensureCollectionDEK(adapter, vault, keyring);
47
- const dek = await getDek(SYNC_CREDENTIALS_COLLECTION);
48
- const { iv, data } = await encrypt(JSON.stringify(credential), dek);
49
- const existing = await adapter.get(vault, SYNC_CREDENTIALS_COLLECTION, credential.adapterId);
50
- const version = existing ? existing._v + 1 : 1;
51
- const envelope = {
52
- _noydb: NOYDB_FORMAT_VERSION,
53
- _v: version,
54
- _ts: (/* @__PURE__ */ new Date()).toISOString(),
55
- _iv: iv,
56
- _data: data,
57
- _by: keyring.userId
58
- };
59
- await adapter.put(
60
- vault,
61
- SYNC_CREDENTIALS_COLLECTION,
62
- credential.adapterId,
63
- envelope,
64
- existing ? existing._v : void 0
65
- );
66
- }
67
- async function getCredential(adapter, vault, keyring, adapterId) {
68
- requireAdminAccess(keyring);
69
- const getDek = await ensureCollectionDEK(adapter, vault, keyring);
70
- const dek = await getDek(SYNC_CREDENTIALS_COLLECTION);
71
- const envelope = await adapter.get(vault, SYNC_CREDENTIALS_COLLECTION, adapterId);
72
- if (!envelope) return null;
73
- const plaintext = await openEnvelopeJson(envelope, dek);
74
- return JSON.parse(plaintext);
75
- }
76
- async function deleteCredential(adapter, vault, keyring, adapterId) {
77
- requireAdminAccess(keyring);
78
- await adapter.delete(vault, SYNC_CREDENTIALS_COLLECTION, adapterId);
79
- }
80
- async function listCredentials(adapter, vault, keyring) {
81
- requireAdminAccess(keyring);
82
- return adapter.list(vault, SYNC_CREDENTIALS_COLLECTION);
83
- }
84
- async function credentialStatus(adapter, vault, keyring, adapterId) {
85
- const credential = await getCredential(adapter, vault, keyring, adapterId);
86
- if (!credential) return { exists: false };
87
- const expired = credential.expiresAt ? Date.now() > new Date(credential.expiresAt).getTime() : false;
88
- return { exists: true, expired };
89
- }
90
-
91
- export {
92
- withTeam,
93
- putCredential,
94
- getCredential,
95
- deleteCredential,
96
- listCredentials,
97
- credentialStatus
98
- };
99
- //# sourceMappingURL=chunk-JZWWLLVY.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-party/team/active.ts","../src/with-party/team/sync-credentials.ts"],"sourcesContent":["/**\n * Enable the multi-user team capability (#267 Track A tail).\n *\n * Pass to `createNoydb({ teamStrategy: withTeam() })` to make `db.grant` /\n * `db.revoke` / `db.rotate` live. The keyring grant/revoke/rotate engines\n * are statically imported HERE — this module is reachable only through the\n * `@noy-db/hub/team` subpath (and the root barrel's tree-shakeable\n * re-export), so a single-user floor bundle never carries them. The facade\n * (`TeamFacade.runGrant` / `runRevoke` / `runRotate`) owns the policy-gate +\n * keyring plumbing and receives the engine as an argument.\n */\nimport type { TeamStrategy } from './strategy.js'\nimport { grant as grantEngine, revoke as revokeEngine, rotateKeys as rotateEngine } from './keyring.js'\n\nexport function withTeam(): TeamStrategy {\n return {\n async grant(team, vault, options, factors) {\n return team.runGrant(grantEngine, vault, options, factors)\n },\n async revoke(team, vault, options, factors) {\n return team.runRevoke(revokeEngine, vault, options, factors)\n },\n async rotate(team, vault, collections) {\n return team.runRotate(rotateEngine, vault, collections)\n },\n }\n}\n","/**\n * _sync_credentials reserved collection —\n *\n * Stores per-adapter OAuth tokens (and any other long-lived sync secrets) as\n * encrypted records inside the vault itself. Tokens are wrapped with the\n * compartment's own DEK, live on disk as ciphertext like any other record, and\n * are accessed only through the dedicated API in this module — never via\n * `vault.collection('_sync_credentials')`.\n *\n * Design decisions\n * ────────────────\n *\n * **Why a reserved collection, not a separate store?**\n * The compartment's existing encryption stack (AES-256-GCM + collection DEK)\n * is exactly the right primitive for protecting OAuth tokens at rest. Using a\n * separate store would require a new encryption surface, new adapter calls,\n * and a new backup/restore path — all of which already exist for collections.\n *\n * **Why not exposed as a regular collection?**\n * The same reason `_keyring` and `_ledger` aren't: they have invariants that\n * must be enforced (naming scheme, no cross-user leakage, no schema\n * validation, no history/ledger writes for privacy). Routing through a\n * dedicated API enforces those invariants.\n *\n * **Token lifecycle:**\n * - `putCredential(vault, adapterId, token)` — store or overwrite\n * - `getCredential(vault, adapterId)` — load and decrypt\n * - `deleteCredential(vault, adapterId)` — remove\n * - `listCredentials(vault)` — enumerate adapter IDs (not tokens)\n *\n * The `adapterId` is the record ID within the `_sync_credentials` collection.\n * It should be a stable, human-readable identifier for the adapter instance\n * (e.g. `'google-drive'`, `'dropbox'`, `'s3-prod'`).\n *\n * **ACL:** only `owner` and `admin` roles can read/write sync credentials.\n * Operators, viewers, and clients cannot call this API. The check is made\n * against the caller's keyring role at call time.\n */\n\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\nimport type { UnlockedKeyring } from './keyring.js'\nimport { encrypt, openEnvelopeJson } from '../../kernel/enclave/index.js'\nimport { ensureCollectionDEK } from './keyring.js'\nimport { PermissionDeniedError } from '../../kernel/errors.js'\n\n/**\n * The reserved collection name. Never collides with user collections.\n * Canonical definition lives in `reserved-secret-collections.ts` (shared with\n * the `vault.collection()` guard and grant DEK-propagation); re-exported here\n * for existing consumers.\n */\nexport { SYNC_CREDENTIALS_COLLECTION } from './reserved-secret-collections.js'\nimport { SYNC_CREDENTIALS_COLLECTION } from './reserved-secret-collections.js'\n\n// ─── Token types ──────────────────────────────────────────────────────\n\n/**\n * An OAuth/auth token stored in `_sync_credentials`.\n *\n * Fields mirror the OAuth2 token response shape. `customData` is an escape\n * hatch for adapter-specific secrets (API keys, connection strings, etc.)\n * that don't fit the OAuth2 shape.\n */\nexport interface SyncCredential {\n /** Stable identifier for the adapter instance (e.g. 'google-drive'). */\n readonly adapterId: string\n /** OAuth token type, usually 'Bearer'. */\n readonly tokenType: string\n /** The access token. Expires at `expiresAt` if set. */\n readonly accessToken: string\n /** Long-lived refresh token for renewing the access token. */\n readonly refreshToken?: string\n /** ISO timestamp when `accessToken` expires. Absent means \"no expiry\". */\n readonly expiresAt?: string\n /** Space-separated OAuth scopes. */\n readonly scopes?: string\n /** Adapter-specific opaque data (API keys, endpoints, etc.). */\n readonly customData?: Record<string, string>\n}\n\n// ─── Access check ─────────────────────────────────────────────────────\n\nfunction requireAdminAccess(keyring: UnlockedKeyring): void {\n // FR-6: custodian is INTENTIONALLY excluded. Sync credentials are the\n // firm's hosting/infrastructure secrets (OAuth tokens, connection strings) —\n // not the custodian's operational scope. A custodian operates the DATA but\n // must never mint or read transport credentials that could be used to\n // re-home or impersonate the firm's vault. Do NOT add 'custodian' here.\n if (keyring.role !== 'owner' && keyring.role !== 'admin') {\n throw new PermissionDeniedError(\n `Sync credentials require owner or admin role. Current role: \"${keyring.role}\"`,\n )\n }\n}\n\n// ─── Public API ────────────────────────────────────────────────────────\n\n/**\n * Store or overwrite a sync credential for the given adapter.\n *\n * The credential is encrypted with the `_sync_credentials` collection DEK\n * (auto-generated on first use). The record ID is the `adapterId`.\n *\n * Requires owner or admin role.\n */\nexport async function putCredential(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n credential: SyncCredential,\n): Promise<void> {\n requireAdminAccess(keyring)\n\n const getDek = await ensureCollectionDEK(adapter, vault, keyring)\n const dek = await getDek(SYNC_CREDENTIALS_COLLECTION)\n\n const { iv, data } = await encrypt(JSON.stringify(credential), dek)\n\n const existing = await adapter.get(vault, SYNC_CREDENTIALS_COLLECTION, credential.adapterId)\n const version = existing ? existing._v + 1 : 1\n\n const envelope: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: version,\n _ts: new Date().toISOString(),\n _iv: iv,\n _data: data,\n _by: keyring.userId,\n }\n\n await adapter.put(\n vault,\n SYNC_CREDENTIALS_COLLECTION,\n credential.adapterId,\n envelope,\n existing ? existing._v : undefined,\n )\n}\n\n/**\n * Load and decrypt a sync credential for the given adapter ID.\n *\n * Returns `null` if no credential exists for this adapter.\n * Requires owner or admin role.\n */\nexport async function getCredential(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n adapterId: string,\n): Promise<SyncCredential | null> {\n requireAdminAccess(keyring)\n\n const getDek = await ensureCollectionDEK(adapter, vault, keyring)\n const dek = await getDek(SYNC_CREDENTIALS_COLLECTION)\n\n const envelope = await adapter.get(vault, SYNC_CREDENTIALS_COLLECTION, adapterId)\n if (!envelope) return null\n\n const plaintext = await openEnvelopeJson(envelope, dek)\n return JSON.parse(plaintext) as SyncCredential\n}\n\n/**\n * Delete a sync credential by adapter ID.\n *\n * No-op if the credential doesn't exist. Requires owner or admin role.\n */\nexport async function deleteCredential(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n adapterId: string,\n): Promise<void> {\n requireAdminAccess(keyring)\n await adapter.delete(vault, SYNC_CREDENTIALS_COLLECTION, adapterId)\n}\n\n/**\n * List all adapter IDs that have stored credentials.\n *\n * Returns only the IDs, never the credential payloads. Useful for\n * displaying \"connected adapters\" in UI without decrypting tokens.\n * Requires owner or admin role.\n */\nexport async function listCredentials(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n): Promise<string[]> {\n requireAdminAccess(keyring)\n return adapter.list(vault, SYNC_CREDENTIALS_COLLECTION)\n}\n\n/**\n * Check whether a credential exists and whether its access token has expired.\n *\n * Returns `{ exists: false }` if no credential is stored, or\n * `{ exists: true, expired: boolean }` based on the `expiresAt` field.\n * Requires owner or admin role.\n */\nexport async function credentialStatus(\n adapter: NoydbStore,\n vault: string,\n keyring: UnlockedKeyring,\n adapterId: string,\n): Promise<{ exists: false } | { exists: true; expired: boolean }> {\n const credential = await getCredential(adapter, vault, keyring, adapterId)\n if (!credential) return { exists: false }\n\n const expired = credential.expiresAt\n ? Date.now() > new Date(credential.expiresAt).getTime()\n : false\n\n return { exists: true, expired }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAcO,SAAS,WAAyB;AACvC,SAAO;AAAA,IACL,MAAM,MAAM,MAAM,OAAO,SAAS,SAAS;AACzC,aAAO,KAAK,SAAS,OAAa,OAAO,SAAS,OAAO;AAAA,IAC3D;AAAA,IACA,MAAM,OAAO,MAAM,OAAO,SAAS,SAAS;AAC1C,aAAO,KAAK,UAAU,QAAc,OAAO,SAAS,OAAO;AAAA,IAC7D;AAAA,IACA,MAAM,OAAO,MAAM,OAAO,aAAa;AACrC,aAAO,KAAK,UAAU,YAAc,OAAO,WAAW;AAAA,IACxD;AAAA,EACF;AACF;;;ACyDA,SAAS,mBAAmB,SAAgC;AAM1D,MAAI,QAAQ,SAAS,WAAW,QAAQ,SAAS,SAAS;AACxD,UAAM,IAAI;AAAA,MACR,gEAAgE,QAAQ,IAAI;AAAA,IAC9E;AAAA,EACF;AACF;AAYA,eAAsB,cACpB,SACA,OACA,SACA,YACe;AACf,qBAAmB,OAAO;AAE1B,QAAM,SAAS,MAAM,oBAAoB,SAAS,OAAO,OAAO;AAChE,QAAM,MAAM,MAAM,OAAO,2BAA2B;AAEpD,QAAM,EAAE,IAAI,KAAK,IAAI,MAAM,QAAQ,KAAK,UAAU,UAAU,GAAG,GAAG;AAElE,QAAM,WAAW,MAAM,QAAQ,IAAI,OAAO,6BAA6B,WAAW,SAAS;AAC3F,QAAM,UAAU,WAAW,SAAS,KAAK,IAAI;AAE7C,QAAM,WAA8B;AAAA,IAClC,QAAQ;AAAA,IACR,IAAI;AAAA,IACJ,MAAK,oBAAI,KAAK,GAAE,YAAY;AAAA,IAC5B,KAAK;AAAA,IACL,OAAO;AAAA,IACP,KAAK,QAAQ;AAAA,EACf;AAEA,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,IACA,WAAW,SAAS,KAAK;AAAA,EAC3B;AACF;AAQA,eAAsB,cACpB,SACA,OACA,SACA,WACgC;AAChC,qBAAmB,OAAO;AAE1B,QAAM,SAAS,MAAM,oBAAoB,SAAS,OAAO,OAAO;AAChE,QAAM,MAAM,MAAM,OAAO,2BAA2B;AAEpD,QAAM,WAAW,MAAM,QAAQ,IAAI,OAAO,6BAA6B,SAAS;AAChF,MAAI,CAAC,SAAU,QAAO;AAEtB,QAAM,YAAY,MAAM,iBAAiB,UAAU,GAAG;AACtD,SAAO,KAAK,MAAM,SAAS;AAC7B;AAOA,eAAsB,iBACpB,SACA,OACA,SACA,WACe;AACf,qBAAmB,OAAO;AAC1B,QAAM,QAAQ,OAAO,OAAO,6BAA6B,SAAS;AACpE;AASA,eAAsB,gBACpB,SACA,OACA,SACmB;AACnB,qBAAmB,OAAO;AAC1B,SAAO,QAAQ,KAAK,OAAO,2BAA2B;AACxD;AASA,eAAsB,iBACpB,SACA,OACA,SACA,WACiE;AACjE,QAAM,aAAa,MAAM,cAAc,SAAS,OAAO,SAAS,SAAS;AACzE,MAAI,CAAC,WAAY,QAAO,EAAE,QAAQ,MAAM;AAExC,QAAM,UAAU,WAAW,YACvB,KAAK,IAAI,IAAI,IAAI,KAAK,WAAW,SAAS,EAAE,QAAQ,IACpD;AAEJ,SAAO,EAAE,QAAQ,MAAM,QAAQ;AACjC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-formula/materialized-views/stale.ts"],"sourcesContent":["import type { Collection } from '../../kernel/collection.js'\nimport type { TxContext } from '../../with-commit/tx/transaction.js'\nimport type { PutDerivedOutputCtx } from '../../kernel/via/dispatch.js'\nimport type { MaterializedViewRegistry, RegisteredMV } from './registry.js'\n// Type-only — runtime class loaded via dynamic import in\n// `resolveStaleMVOnRead` only when a stale flag actually fires.\n// Keeps the executor chunk out of the floor bundle (mirrors v1 floor-bundle isolation).\nimport type { MaterializedViewExecutor as MVExecutorType } from './executor.js'\nimport type { MVQueryContext } from './types.js'\nimport type { NoydbStore } from '../../kernel/types.js'\nimport { RecordCodec } from '../../kernel/enclave/index.js'\n\n/**\n * Accessor shape passed in from the owning Vault. Provides the\n * registry (used as a stable WeakMap key + to look up MVs by output\n * collection) and the runtime context the lazy refresh needs.\n * Mirrors v1's `DerivationStaleAccessor`.\n */\nexport interface MVStaleAccessor {\n registry(): MaterializedViewRegistry\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n getCollection(name: string): Collection<any>\n getActiveTxContext(): TxContext | null\n getQueryContext(): MVQueryContext\n}\n\n/**\n * In-memory stale map keyed by `MaterializedViewRegistry` instance\n * (stable per vault). Each registry maps to a set of MV names that\n * have at least one pending source-change requiring a re-materialize.\n *\n * Persistence across vault close is NOT implemented in this iteration\n * (concern flagged in the v2 spec, mirrors v1 derivation behavior).\n * On vault re-open, the unset stale flag is interpreted as \"fresh\" —\n * `vault.refreshView(name)` is the explicit recompute escape hatch.\n *\n * @internal\n */\nconst _staleByRegistry = new WeakMap<MaterializedViewRegistry, Set<string>>()\n\n/**\n * Mark an MV as stale. Called from `Collection.dispatchMaterializedViews`\n * when a source-write fires for a `refresh: 'lazy'` MV.\n *\n * @internal\n */\nexport function markMVStale(registry: MaterializedViewRegistry, mvName: string): void {\n let set = _staleByRegistry.get(registry)\n if (!set) {\n set = new Set()\n _staleByRegistry.set(registry, set)\n }\n set.add(mvName)\n}\n\n/**\n * Test-only: check whether a given MV name is currently flagged stale\n * against a registry. Exported so the regression suite can pin the\n * stale-bit lifecycle without touching the internal `WeakMap`.\n *\n * @internal\n */\nexport function isMVStale(registry: MaterializedViewRegistry, mvName: string): boolean {\n return _staleByRegistry.get(registry)?.has(mvName) ?? false\n}\n\n/**\n * Reserved collection holding CONTENT-FREE lazy-stale markers (record id =\n * MV name, plaintext `_iv: ''` envelope, no payload) — mirrors the\n * `_meta`/schema-fence marker envelope shape. Written only from the\n * delete/forget/elevate dispatcher (#736); ordinary source writes stay\n * in-memory-only (the cheap path). Read via the adapter directly, never\n * through `vault.collection()` — same reserved-collection convention as\n * `_subject_index`/`_meta`.\n */\nexport const MV_STALE_COLLECTION = '_mv_stale'\n\n/**\n * Once-per-registry hydrate guard for `resolveStaleMVOnRead`'s persisted-marker fold-in.\n * Stores the in-flight/settled PROMISE (not a boolean) — a concurrent cold first read that\n * arrives while the hydrate is still awaiting `adapter.list` must await the SAME promise\n * rather than observing \"already hydrating\" and reading an empty pending set (#736\n * whole-branch review).\n */\nconst _hydratedByRegistry = new WeakMap<MaterializedViewRegistry, Promise<void>>()\n\n/** The adapter + vault name backing any collection in this vault (all collections share both). */\nfunction storeOf(accessor: MVStaleAccessor, collectionName: string): { adapter: NoydbStore; vault: string } {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const c = accessor.getCollection(collectionName) as any\n return { adapter: c.adapter as NoydbStore, vault: c.vault as string }\n}\n\nasync function writeMVStaleMarker(adapter: NoydbStore, vault: string, mvName: string): Promise<void> {\n // Content-free — the marker's only payload is its key (the MV name).\n const env = RecordCodec.buildPlaintextEnvelope({ version: 1, data: '{}' })\n await adapter.put(vault, MV_STALE_COLLECTION, mvName, env)\n}\n\nasync function deleteMVStaleMarker(adapter: NoydbStore, vault: string, mvName: string): Promise<void> {\n await adapter.delete(vault, MV_STALE_COLLECTION, mvName)\n}\n\n/**\n * On first `resolveStaleMVOnRead` call for a (cold-reopened) registry, fold every\n * persisted `_mv_stale` marker into the in-memory pending set — otherwise a cold\n * session's empty `WeakMap` entry reads as \"fresh\" and serves the emptied output\n * collection forever.\n */\nasync function hydratePersistedStaleMarkers(accessor: MVStaleAccessor, registry: MaterializedViewRegistry): Promise<void> {\n const mvs = registry.all()\n if (mvs.length === 0) return\n const { adapter, vault } = storeOf(accessor, mvs[0]!.outputCollection)\n // No try/catch: a genuine store failure here must surface, not be read as\n // \"nothing pending\" — silently treating it as fresh is the exact leak class\n // this module closes (mirrors `subject-index.ts`'s bare `adapter.list` reads).\n for (const name of await adapter.list(vault, MV_STALE_COLLECTION)) markMVStale(registry, name)\n}\n\n/**\n * Invalidate an MV from the delete/forget/elevate dispatcher (#736) — the ordinary\n * source-write path (`dispatchMaterializedViews`) never calls this. Deletes EVERY\n * persisted row in `reg.outputCollection` via `_internalDelete` (the at-rest law: a\n * stale mark alone leaves the elevated/forgotten source's plaintext sitting in the\n * output row) using the adapter directly so the read-path's `resolveStaleMVOnRead`\n * isn't triggered mid-invalidation. `mode: 'lazy'` also persists the stale mark so a\n * cold session recomputes on next read; `'manual'` gets the purge only — the MV\n * serves empty until an explicit `vault.refreshView()` (erasure wins). Deletion is\n * stamp-scoped (`_materializedFrom.mvName === reg.spec.name` — see the loop below),\n * so another registered MV sharing this output collection keeps its own rows intact:\n * no cross-MV re-marking is needed here, only `reg` itself is marked stale.\n *\n * Returns the count of rows actually `_internalDelete`d (#761 item 1) — folded by the\n * caller into `dispatchMaterializedViewsOnDelete`'s `deleted` so `ForgetResult.derivedRecordsErased`\n * counts lazy/manual purges too, not just the eager executor's tombstone leg — plus\n * `residueUndecodable`, the bare ids whose ownership stamp could NOT be decoded (#776): a\n * candidate row that fails to decode under the collection's default DEK (e.g. elevated above\n * tier 0 on a tiered output collection) has UNKNOWN ownership — it might be this MV's own\n * output, or (same-collection shape) a plain user record — so it is never erased, but it must\n * be SURFACED rather than silently skipped (the #724 posture), since it may still hold the\n * forgotten/pre-elevation contribution, decryptable by tier-holders — and `residueDeclined`\n * (#785), the bare ids that DID decode and stamp-match but whose `_internalDelete` declined\n * (ownership CONFIRMED, erasure declined).\n *\n * @internal\n */\nexport async function invalidateMVAtRest(\n accessor: MVStaleAccessor,\n reg: RegisteredMV,\n mode: 'lazy' | 'manual',\n): Promise<{ deleted: number; residueUndecodable: string[]; residueDeclined: string[] }> {\n const outputColl = accessor.getCollection(reg.outputCollection)\n const txCtx = accessor.getActiveTxContext()\n const { adapter, vault } = storeOf(accessor, reg.outputCollection)\n let deleted = 0\n const residueUndecodable: string[] = []\n const residueDeclined: string[] = []\n for (const id of await adapter.list(vault, reg.outputCollection)) {\n // A same-collection partition MV (`output: { collection: <source>, partition }`,\n // the DERIV-PP30-001 shape) writes INTO its own source collection — `adapter.list`\n // over `reg.outputCollection` then also returns untouched user source records.\n // Decode each candidate row (the invalidation path is rare; this cost is fine\n // here) and only erase rows THIS MV stamped via `_materializedFrom.mvName` —\n // an unstamped row, or one stamped by a different MV, is never this MV's to\n // delete (#736 whole-branch review, Critical).\n const envelope = await adapter.get(vault, reg.outputCollection, id)\n if (envelope === null) continue\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const decoded = await (outputColl as any)._decodeEnvelope(envelope, id)\n if (decoded === null) { residueUndecodable.push(id); continue } // #776 — ownership unknown, surfaced not skipped\n const stampedBy = (decoded as Record<string, unknown>)._materializedFrom as { mvName?: string } | undefined\n if (stampedBy?.mvName !== reg.spec.name) continue\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n if (await (outputColl as any)._internalDelete(id, txCtx)) {\n deleted++\n } else {\n // #782 part b — decoded AND stamp-owned, but erasure declined (#718 tier-elevation\n // gate). Ownership IS confirmed — a real silent survival, not a legit stamp-mismatch\n // skip. Surface it too, in its own channel (#785 — distinct from the decode-null case).\n residueDeclined.push(id)\n }\n }\n\n // `mode: 'manual'` gets the purge only (erasure wins, no auto-recompute promise);\n // `'lazy'` also marks + persists ITS OWN stale bit for cold-session recompute. No\n // sibling MV on the same output collection is touched here — the stamp-scoped\n // deletion above never erased sibling rows in the first place (#736 re-review).\n if (mode === 'lazy') {\n const registry = accessor.registry()\n markMVStale(registry, reg.spec.name)\n await writeMVStaleMarker(adapter, vault, reg.spec.name)\n }\n return { deleted, residueUndecodable, residueDeclined }\n}\n\n/**\n * Called from `Collection.get` (and any reader that materializes the\n * MV's output collection). If any MV producing `outputCollection` is\n * flagged stale, runs the executor against the live source state\n * before returning. No-op when there is no pending work — keeps the\n * read fast path negligible.\n *\n * `dispatchCtx` (#641): threaded from the calling `Collection`'s\n * `#dispatchCtx({ collection: outputCollection, id: 'resolve-on-read' })` — a read has no\n * real \"reacting write\" record, so `'resolve-on-read'` is the sentinel id, mirroring\n * `Vault.refreshView()`'s `'refreshView'` sentinel for the same reason. Passed straight\n * through to the executor so a stale row whose output lands in a closed period follows the\n * frozen-output rule (skip + `derivation:skipped-frozen`, #637) instead of throwing\n * `PeriodClosedError` out of a read.\n *\n * Dynamic-imports the executor only when a stale flag actually fires\n * (the floor-bundle isolation pattern v1 derivations established in\n * floor-bundle isolation pattern).\n */\nexport async function resolveStaleMVOnRead(\n accessor: MVStaleAccessor,\n outputCollection: string,\n dispatchCtx?: PutDerivedOutputCtx,\n): Promise<void> {\n const registry = accessor.registry()\n // Cold session: the in-memory WeakMap has no entry for this (freshly\n // constructed) registry yet — fold any persisted `_mv_stale` markers in\n // before reading the pending set, once per registry (#736). Memoize the\n // PROMISE, not a boolean: a concurrent first read arriving while the\n // hydrate is still awaiting `adapter.list` must await the SAME promise —\n // otherwise it observes \"already hydrating\", skips straight to an empty\n // pending set, and serves the purged/stale view as fresh (#736\n // whole-branch review, hydrate-once race). On rejection, evict the entry\n // BEFORE rethrowing — a cached rejected promise would otherwise poison the\n // MV read surface for the rest of the session (every later read re-awaits\n // the same rejection instead of retrying the store call) (#736 re-review).\n let hydrate = _hydratedByRegistry.get(registry)\n if (!hydrate) {\n hydrate = hydratePersistedStaleMarkers(accessor, registry).catch((err: unknown) => {\n _hydratedByRegistry.delete(registry)\n throw err\n })\n _hydratedByRegistry.set(registry, hydrate)\n }\n await hydrate\n const pending = _staleByRegistry.get(registry)\n if (!pending || pending.size === 0) return\n\n const { adapter, vault } = storeOf(accessor, outputCollection)\n\n // Clean up persisted markers for MV names no longer registered (renamed or\n // removed since the marker was written). Without this sweep an orphaned name\n // can never become a `candidate` below — `candidates` is built from\n // `registry.all()`, which no longer lists it — so both the in-memory pending\n // entry and the persisted `_mv_stale` row would linger forever, re-hydrating\n // on every cold session (#736 whole-branch review).\n for (const name of pending) {\n if (registry.byName(name)) continue\n pending.delete(name)\n await deleteMVStaleMarker(adapter, vault, name)\n }\n if (pending.size === 0) return\n\n // Find every MV that writes to this output collection AND is\n // currently flagged stale. Multiple MVs CAN share an output\n // collection in theory; in practice the registration validation +\n // cycle detection make this unusual.\n const candidates: string[] = []\n for (const mv of registry.all()) {\n if (mv.outputCollection !== outputCollection) continue\n if (!pending.has(mv.spec.name)) continue\n candidates.push(mv.spec.name)\n }\n if (candidates.length === 0) return\n\n let executor: typeof MVExecutorType | null = null\n for (const name of candidates) {\n const reg = registry.byName(name)\n if (!reg) {\n // Unreachable in practice — `candidates` is drawn from `registry.all()`,\n // and the orphan sweep above already cleared any name absent from it.\n // Kept as a defensive fallback; also clears the persisted marker so it\n // can't linger if this ever does fire.\n pending.delete(name)\n await deleteMVStaleMarker(adapter, vault, name)\n continue\n }\n if (executor === null) {\n ({ MaterializedViewExecutor: executor } = (await import('./executor.js')) as {\n MaterializedViewExecutor: typeof MVExecutorType\n })\n }\n // A cold-hydrated stale flag (unlike an in-session one) can fire with\n // NONE of the MV's dependency collections touched yet this session —\n // the sync `Query.toArray()` `spec.query()` runs reads straight off\n // `Collection`'s in-memory cache, populated only by a prior async\n // touch (`ensureHydrated()`, called from `get`/`list`/`put`, never\n // from `query()` itself). Warm every dependency first so the recompute\n // sees the live persisted state instead of an empty cache (#736).\n for (const dep of reg.dependencies) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n await (accessor.getCollection(dep) as any).ensureHydrated()\n }\n await executor.refresh(reg, {\n getCollection: (n) => accessor.getCollection(n),\n getActiveTxContext: () => accessor.getActiveTxContext(),\n getQueryContext: () => accessor.getQueryContext(),\n ...(dispatchCtx !== undefined ? { dispatchCtx } : {}),\n })\n pending.delete(name)\n // Runs even when no marker was ever persisted for this name (an in-session-only\n // stale bit — e.g. the cheap ordinary-write path never writes one, see the\n // \"cheap-path guarantee\" test). `adapter.delete` on an absent key is a harmless\n // void — keeping this unconditional avoids branching on marker origin here.\n await deleteMVStaleMarker(adapter, vault, name)\n }\n}\n\n/**\n * Drop every stale flag for a registry. Used after a manual\n * `vault.refreshView(name)` runs the executor explicitly — the\n * post-refresh state matches the registered strategies, so\n * lingering stale bits would force a redundant refresh on the next\n * read.\n *\n * @internal\n */\nexport function clearMVStale(registry: MaterializedViewRegistry, mvName: string): void {\n _staleByRegistry.get(registry)?.delete(mvName)\n}\n\n/**\n * `clearMVStale` plus the persisted marker (#736) — `Vault.refreshView()`'s\n * completion path. A lingering persisted marker after a manual refresh would\n * make the NEXT cold session recompute redundantly.\n *\n * @internal\n */\nexport async function clearMVStaleFully(\n adapter: NoydbStore,\n vault: string,\n registry: MaterializedViewRegistry,\n mvName: string,\n): Promise<void> {\n clearMVStale(registry, mvName)\n await deleteMVStaleMarker(adapter, vault, mvName)\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-audit/forget/strategy.ts","../src/with-audit/forget/subject-index.ts"],"sourcesContent":["/**\n * `withForgetCascade` — declaration surface for GDPR right-to-erasure via\n * per-record CEK crypto-shred.\n *\n * This file holds only the *declaration* shape and the disabled sentinel.\n * The actual erasure machinery lives in:\n * - `subject-index.ts` — the encrypted `_subject_index` reserved collection\n * - `vault.ts` `forget()` — the per-record tombstone + ledger flow\n * - `collection.ts` `_writeTombstone` — the envelope rewrite\n *\n * A `ForgetStrategy` declares which collections carry erasable subject data\n * and the (dotted-path) field on each record that names the data subject.\n * Declaring a collection here ALSO forces `perRecordKeys: true` for it (a\n * shred can only erase a record whose body is keyed off a per-record CEK),\n * so adopters opt into the CEK foundation transitively.\n *\n * @module\n */\nimport type { LedgerEntry } from '../../with-commit/history/ledger/entry.js'\n\n/**\n * User-supplied declaration passed to {@link withForgetCascade}. Maps a\n * collection name to the record field (dotted path supported, e.g.\n * `'billing.buyerId'`) that identifies the data subject for erasure.\n *\n * ```ts\n * withForgetCascade({ subjects: { invoices: 'buyerId', contacts: 'id' } })\n * ```\n */\nexport interface SubjectDeclaration {\n readonly subjects: Record<string, string>\n /**\n * #633 — opt-in: gate the vault-level `_sealed_cek` and blob purges on\n * per-collection via declarations instead of running them unconditionally\n * for every forgotten ref. Default `false`/absent = today's unconditional\n * behavior (byte-identical). See `purge-scope.ts` for the partition logic\n * and the `scopedPurgeResidue` skip-reporting this enables.\n *\n * **Footgun:** a bare `sensitive: [...]` collection — no `classifiedFields`\n * (no classified via binder compiled in) — counts as UNDECLARED for the\n * sealed-CEK arm. Under `scopedPurge: true`, its `_sealed_cek` host-delivery\n * envelopes are SKIPPED, not purged (reported via `scopedPurgeResidue`,\n * reason `'skipped-undeclared-sealed-cek'`) even if that collection called\n * `sealRecordToHost()` — the sealed CEK stays recoverable by that granted\n * host until purged. Add a `classifiedFields` binding to close the gap, or\n * leave `scopedPurge` off/false for the unconditional (always-purged) default.\n * The declaration signal is session-local: a declared collection never opened\n * (with its config) before `forget()` in this session counts as UNDECLARED —\n * open it first, or its entries are skipped-and-reported.\n */\n readonly scopedPurge?: boolean\n}\n\n/**\n * Resolved forget strategy threaded through Noydb → every Vault. Carries\n * the same `subjects` map the user declared. `NO_FORGET` (empty map) is the\n * off-by-default sentinel; `vault.forget()` throws\n * `ForgetStrategyNotConfiguredError` when the map is empty.\n */\nexport interface ForgetStrategy {\n /** Collection → subject-field (dotted path). Empty under `NO_FORGET`. */\n readonly subjects: Readonly<Record<string, string>>\n /** #633 — see {@link SubjectDeclaration.scopedPurge}. */\n readonly scopedPurge?: boolean\n}\n\n/**\n * Disabled sentinel — no collections declare a subject field. `vault.forget()`\n * refuses with `ForgetStrategyNotConfiguredError`; no write hooks register; no\n * collection is forced into `perRecordKeys`. Non-adopters pay nothing.\n */\nexport const NO_FORGET: ForgetStrategy = { subjects: {} }\n\n// The `withForgetCascade` factory lives in `active.ts` (canonical\n// strategy.ts/active.ts/index.ts split); this file holds only the\n// declaration shape + disabled sentinel.\n\n/**\n * The outcome of a `vault.forget(subjectId)` call.\n *\n * `unmigratedRecords` lists `collection:id` pairs that were tombstoned but\n * whose body had NOT been migrated to a per-record CEK at shred time (legacy\n * body still under the shared collection DEK). Those records are tombstoned\n * (live envelope + history stripped) but their pre-shred ciphertext, if it\n * leaked into a backup before migration, remains decryptable under the\n * collection DEK — so erasure-completeness is NOT guaranteed for them. Run\n * the per-record-CEK migration pass, then re-forget, to close the gap.\n *\n * Blob attachments: a shredded record's **erasable** blobs (on a\n * `perRecordKeys` collection) are crypto-shredded inline — `blobsShredded`\n * counts those taken to refCount 0 (BlobObject deleted → chunks permanently\n * undecryptable), `blobsRetainedShared` counts those still referenced by\n * another record (shared content legitimately persists for its other owner).\n * `blobResidueCollections` now lists only collections with blobs that could\n * NOT be crypto-shredded: **legacy** blobs (no per-blob `_cek`, chunks under\n * the shared `_blob` DEK — migrate them), or a session without the blob\n * service loaded. An all-erasable subject yields an empty residue list.\n */\nexport interface ForgetResult {\n /** The subject id passed to `forget()`. Echoed for caller convenience. */\n readonly subject: string\n /** Count of live records rewritten to a tombstone. */\n readonly recordsShredded: number\n /** Count of `_history` envelopes tombstoned across all shredded records. */\n readonly historyVersionsShredded: number\n /** Distinct collections that had at least one record shredded. */\n readonly collections: readonly string[]\n /** `collection:id` pairs shredded while still un-migrated (see type docs). */\n readonly unmigratedRecords: readonly string[]\n /** Count of erasable blobs crypto-shredded (refCount → 0, BlobObject deleted). */\n readonly blobsShredded: number\n /** Count of erasable blobs retained because still referenced elsewhere (shared). */\n readonly blobsRetainedShared: number\n /** Collections with blobs that could NOT be crypto-shredded — legacy (no `_cek`) or blobs disabled (see type docs). */\n readonly blobResidueCollections: readonly string[]\n /**\n * Count of persisted `_idx/<field>/<recordId>` index side-cars hard-deleted\n * across the shredded records. These live under the retained\n * collection DEK, so crypto-shred alone would leave the indexed field VALUES\n * readable — `forget()` must delete them.\n */\n readonly indexPostingsPurged: number\n /**\n * `collection:id:field` entries whose persisted `_idx` side-car could NOT be\n * deleted — index residue that still leaks the indexed value under the\n * retained collection DEK. Non-empty means erasure is INCOMPLETE: retry, or\n * purge the side-car out of band.\n */\n readonly indexResidue: readonly string[]\n /**\n * Count of `_sealed[field]` slots dropped from the live store across the\n * shredded records. For slots written under `sensitive` +\n * `perRecordKeys` (the current path), the key derives off the per-record CEK,\n * so tombstoning the record — which drops `_cek` and `_sealed` — also\n * crypto-shreds the value. A legacy slot (written before per-record CEKs) keys\n * off the collection DEK instead, so dropping it removes it from the live store but a\n * pre-forget backup remains recoverable by a DEK holder (same caveat `_data`\n * carries); migrate by re-`put`ting before forgetting for full crypto-shred.\n */\n readonly sealedFieldsShredded: number\n /** Count of `_sealed_cek` host-delivery envelopes deleted (#H-1). A record sealed to an\n * at-* host via sealRecordToHost persists its raw CEK there; forget() must destroy them. */\n readonly sealedCekEnvelopesPurged: number\n /** `collection:id` whose `_sealed_cek` purge failed (residue — the host-recoverable CEK may survive). */\n readonly sealedCekResidue: readonly string[]\n /** `collection:id:field` sealed slots that were DEK-derived (legacy, written before per-record CEKs) and thus NOT crypto-shredded\n * by dropping `_cek` — the collection DEK is retained, so synced/backup copies stay decryptable (#M-1). */\n readonly sealedResidue: readonly string[]\n /** Count of `_ledger_deltas` rows hard-deleted across the shredded records (#734) — the\n * erasure twin of #729's elevate-side purge. Entry metadata (that the record was mutated,\n * at which version/timestamp/actor) is retained; only the plaintext delta content is removed. */\n readonly ledgerDeltasPurged: number\n /** `collection:id` refs whose `_ledger_deltas` purge failed — plaintext delta residue still\n * readable under the retained ledger DEK. Non-empty means erasure is INCOMPLETE. */\n readonly ledgerDeltaResidue: readonly string[]\n /** The single `op:'forget'` ledger entry appended for this erasure. */\n readonly ledgerEntry: LedgerEntry\n /** #622 — record-grain derived artifacts (MV rows, per-record derived copies) erased\n * because their source subject was forgotten. Overlay outputs are intentionally out of\n * scope here — an overlay is always sourced from an MV, never directly from a subject\n * collection, so the personal-data-bearing MV rows it reads are already erased above. */\n readonly derivedRecordsErased: number\n /** #622 — aggregate-grain targets (rollups) recomputed without the forgotten contribution. */\n readonly derivedAggregatesRecomputed: number\n /** #622 — `collection:id` derived writes SKIPPED because the target period is frozen\n * (recompute deferred; the aggregate retains the forgotten contribution — audited). */\n readonly derivedResidueFrozen: readonly string[]\n /** #650 Task 5 (#648) — referencing records tombstoned via a `'ref'` edge's `cascade` policy. */\n readonly lookupReferencesCascaded: number\n /** #650 Task 5 (#648) — referencing fields cleared via a `'ref'` edge's `nullify` policy. */\n readonly lookupReferencesNullified: number\n /** #650 Task 5 review (Important fix) — `'ref'` edges whose compare-key could NOT be resolved,\n * even from the LIVE pre-shred backing row — cascade/nullify propagation was SKIPPED for these.\n * Always empty in the ordinary case; non-empty means the skip is reported, never silent. */\n readonly lookupReferencesResidue: readonly string[]\n /** #633 — scoped-purge skip notices (opt-in `scopedPurge`, see {@link SubjectDeclaration}).\n * Always empty under the unconditional default. Non-empty means a `_sealed_cek` entry or a\n * blob scan was skipped for an undeclared collection — reported, never a silent skip. */\n readonly scopedPurgeResidue: readonly ScopedPurgeResidueNotice[]\n /** #776/#782/#785 — `collection:id` MV-output rows that survived erasure invalidation (eager\n * tombstone leg AND lazy/manual `invalidateMVAtRest`) despite belonging to the forgotten\n * subject, because the `_materializedFrom` ownership stamp could NOT be decoded (undecodable\n * under the collection's default DEK — e.g. elevated above tier 0 on a tiered output\n * collection; ownership unconfirmed — could be a plain user record on a same-collection\n * partition MV). Never erased, but surfaced here rather than silently skipped (the #724\n * posture). Non-empty means the row may still hold the forgotten/pre-elevation contribution,\n * decryptable by tier-holders. */\n readonly derivedResidueUndecodable: readonly string[]\n /** #782/#785 — `collection:id` MV-output rows that DID decode and stamp-match via\n * `_materializedFrom`, but whose erasure was declined by the #718 tier-elevation gate\n * (`_internalDelete` returned false). Ownership CONFIRMED here — a real silent survival,\n * not a stamp-mismatch skip — surfaced rather than dropped. Non-empty means a live,\n * tier-holder-decryptable copy of the forgotten contribution was deliberately retained. */\n readonly derivedResidueDeclined: readonly string[]\n}\n\n/** #633 — the two `scopedPurgeResidue` skip reasons. Single source of truth: `purge-scope.ts`\n * (the port-internal partition helpers) imports this rather than redeclaring it. */\nexport type ScopedPurgeResidueReason = 'skipped-undeclared-sealed-cek' | 'skipped-undeclared-blob-scan'\n\n/** #633 — one `ForgetResult.scopedPurgeResidue` entry: an undeclared collection's sealed-CEK\n * entries left unpurged, or its blob scan skipped entirely, under `scopedPurge`. `count` is the\n * number of `_sealed_cek` entries left in place (sealed-cek reason) or the number of refs whose\n * blob scan was skipped (blob-scan reason) — aggregated per collection across the whole call. */\nexport interface ScopedPurgeResidueNotice {\n readonly reason: ScopedPurgeResidueReason\n readonly collection: string\n readonly count: number\n}\n","/**\n * The encrypted subject index.\n *\n * GDPR crypto-shred needs to answer \"which records belong to data subject\n * X?\" portably (the index must travel with the vault/bundle) WITHOUT leaking\n * subject-equivalence to the store. The rejected alternative — an unencrypted\n * subject tag in envelope metadata — would let anyone with store access see\n * which records share a subject. Instead we keep a reserved `_subject_index`\n * collection, encrypted under its OWN DEK (`getDEK('_subject_index')`):\n *\n * - record id = `HMAC-SHA256(indexDEK, subjectId)` (M-2). A bare\n * `sha256Hex(subjectId)` would be offline-computable: an attacker with\n * store access and a candidate list (emails / customer ids are low-entropy)\n * could dictionary the hash to confirm \"subject X is present here.\" Keying\n * the id with the vault-only index DEK removes that capability — without the\n * DEK the id cannot be derived. (Legacy entries written before M-2 used the\n * bare sha256 id; the read/remove paths dual-look-up both forms.)\n * - record body = AES-GCM(JSON `{ r: [{ collection, id }], p }`) under the\n * index DEK, where `p` pads the plaintext to a bucketed length so the\n * ciphertext `_data` length does not leak the approximate record count.\n * Legacy bodies were a bare `[{ collection, id }]` array; reads accept both.\n *\n * ## Concurrency (RISK #3 — known v1 limitation)\n *\n * `addSubjectRef` / `removeSubjectRef` are read-modify-write with no CAS. The\n * design assumes a SINGLE WRITER (the noy-db single-process write model). Two\n * concurrent writers racing on the SAME subject can lose an entry (last-write\n * wins on the ref list). This is documented, not fixed in v1:\n * `rebuildSubjectIndex` performs a full scan to recover a correct index from\n * the canonical records, so a lost ref is recoverable. A CAS-backed index is\n * deferred to a later slice.\n *\n * @module\n */\nimport { encrypt, openEnvelopeJson, hmacSha256Hex, sha256Hex, type EnclaveKey } from '../../kernel/enclave/index.js'\nimport type { NoydbStore, EncryptedEnvelope } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\n\n/** Reserved collection holding the encrypted subject → records index. */\nexport const SUBJECT_INDEX_COLLECTION = '_subject_index'\n\n/**\n * Bucket (bytes) the encrypted ref-list plaintext is padded up to, so the\n * ciphertext `_data` length leaks only `count` rounded up to a bucket — not the\n * exact record count. 256 keeps small subjects (the common case) indistinguishable.\n */\nconst REF_LIST_BUCKET = 256\n\n/** A single record reference held in a subject's index entry. */\nexport interface SubjectRef {\n readonly collection: string\n readonly id: string\n}\n\ntype GetDEK = (collectionName: string) => Promise<EnclaveKey>\n\n/** SHA-256 hex of a UTF-8 string. The LEGACY (pre-M-2) subject-index key. */\nasync function sha256HexString(input: string): Promise<string> {\n return sha256Hex(new TextEncoder().encode(input))\n}\n\n/**\n * The subject-index record id(s) to consult for a subject, most-current first.\n *\n * - Encrypted vault: the PRIMARY id is `HMAC-SHA256(indexDEK, subjectId)` (M-2)\n * — not offline-computable. The LEGACY `sha256Hex(subjectId)` id is also\n * returned so reads/removes still find entries written before M-2 (dual-lookup).\n * - Plaintext/debug vault: no DEK to key with, so the only id is the legacy\n * sha256 form (unchanged behaviour — plaintext mode is not zero-knowledge anyway).\n */\nasync function subjectKeys(getDEK: GetDEK, encrypted: boolean, subjectId: string): Promise<string[]> {\n const legacy = await sha256HexString(subjectId)\n if (!encrypted) return [legacy]\n const dek = await getDEK(SUBJECT_INDEX_COLLECTION)\n const keyed = await hmacSha256Hex(dek, new TextEncoder().encode(subjectId))\n return keyed === legacy ? [keyed] : [keyed, legacy]\n}\n\n/** The id new writes land under (keyed when encrypted, else legacy sha256). */\nasync function primarySubjectKey(getDEK: GetDEK, encrypted: boolean, subjectId: string): Promise<string> {\n return (await subjectKeys(getDEK, encrypted, subjectId))[0]!\n}\n\n/** Parse a stored ref-list body: new padded `{ r, p }` wrapper OR legacy bare array. */\nfunction parseRefs(json: string): SubjectRef[] {\n const parsed = JSON.parse(json) as SubjectRef[] | { r: SubjectRef[] }\n return Array.isArray(parsed) ? parsed : parsed.r\n}\n\n/** Serialize + pad the ref list to a bucket boundary (encrypted vaults only). */\nfunction serializeRefs(refs: SubjectRef[]): string {\n const base = JSON.stringify({ r: refs, p: '' })\n const pad = Math.ceil(base.length / REF_LIST_BUCKET) * REF_LIST_BUCKET - base.length\n return JSON.stringify({ r: refs, p: ' '.repeat(pad) })\n}\n\n/** Read + decrypt the ref list at a SINGLE index key. Returns `[]` when absent. */\nasync function readRefs(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n key: string,\n): Promise<SubjectRef[]> {\n const env = await adapter.get(vault, SUBJECT_INDEX_COLLECTION, key)\n if (!env || !env._data) return []\n if (!encrypted) return parseRefs(env._data)\n const dek = await getDEK(SUBJECT_INDEX_COLLECTION)\n const json = await openEnvelopeJson(env, dek)\n return parseRefs(json)\n}\n\n/** Encrypt + write a ref list for a subject under its derived key. */\nasync function writeRefs(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n key: string,\n refs: SubjectRef[],\n): Promise<void> {\n let env: EncryptedEnvelope\n if (!encrypted) {\n // Plaintext/debug vault: keep the legacy bare-array form (no padding needed).\n env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: new Date().toISOString(), _iv: '', _data: JSON.stringify(refs) }\n } else {\n const dek = await getDEK(SUBJECT_INDEX_COLLECTION)\n const { iv, data } = await encrypt(serializeRefs(refs), dek)\n env = { _noydb: NOYDB_FORMAT_VERSION, _v: 1, _ts: new Date().toISOString(), _iv: iv, _data: data }\n }\n await adapter.put(vault, SUBJECT_INDEX_COLLECTION, key, env)\n}\n\n/**\n * Add a `{ collection, id }` ref to a subject's index entry (idempotent —\n * a duplicate ref is not appended). Read-modify-write; see the concurrency\n * note in the module docstring.\n */\nexport async function addSubjectRef(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjectId: string,\n ref: SubjectRef,\n): Promise<void> {\n const key = await primarySubjectKey(getDEK, encrypted, subjectId)\n const refs = await readRefs(adapter, vault, getDEK, encrypted, key)\n if (refs.some((r) => r.collection === ref.collection && r.id === ref.id)) return\n refs.push(ref)\n await writeRefs(adapter, vault, getDEK, encrypted, key, refs)\n}\n\n/**\n * Remove a `{ collection, id }` ref from a subject's index entry. When the\n * last ref is removed the (now empty) entry is deleted so the store holds no\n * residual key for an erased subject.\n */\nexport async function removeSubjectRef(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjectId: string,\n ref: SubjectRef,\n): Promise<void> {\n // Dual-lookup: drop the ref from BOTH the keyed (M-2) and the legacy sha256\n // entry, so a pre-M-2 subject is still fully erased.\n for (const key of await subjectKeys(getDEK, encrypted, subjectId)) {\n const refs = await readRefs(adapter, vault, getDEK, encrypted, key)\n const next = refs.filter((r) => !(r.collection === ref.collection && r.id === ref.id))\n if (next.length === refs.length) continue\n if (next.length === 0) {\n await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, key)\n } else {\n await writeRefs(adapter, vault, getDEK, encrypted, key, next)\n }\n }\n}\n\n/**\n * Look up every record ref for a subject. Returns `[]` when none exist. Unions\n * the keyed (M-2) and legacy sha256 entries (dual-lookup), deduplicated, so a\n * subject indexed before M-2 is still fully found.\n */\nexport async function lookupSubject(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjectId: string,\n): Promise<SubjectRef[]> {\n const keys = await subjectKeys(getDEK, encrypted, subjectId)\n const seen = new Set<string>()\n const out: SubjectRef[] = []\n for (const key of keys) {\n for (const ref of await readRefs(adapter, vault, getDEK, encrypted, key)) {\n const dedup = `${ref.collection}\\u0000${ref.id}`\n if (seen.has(dedup)) continue\n seen.add(dedup)\n out.push(ref)\n }\n }\n return out\n}\n\n/**\n * Rebuild the entire subject index from the canonical records (the recovery\n * path for the documented read-modify-write race). Scans each declared\n * collection, reads `record[subjectField]` (dotted path), and rewrites the\n * index from scratch. Tombstoned (already-shredded) records contribute no\n * ref — their body is gone, so they cannot be re-indexed.\n *\n * `decodeRecord` decrypts an envelope to a plain object (or returns null for\n * a tombstone / unreadable record); supplied by the caller so this module\n * stays free of Collection internals.\n */\nexport async function rebuildSubjectIndex(\n adapter: NoydbStore,\n vault: string,\n getDEK: GetDEK,\n encrypted: boolean,\n subjects: Readonly<Record<string, string>>,\n decodeRecord: (collection: string, id: string, env: EncryptedEnvelope) => Promise<Record<string, unknown> | null>,\n): Promise<number> {\n // Drop every existing index entry first so removed refs don't linger.\n const existing = await adapter.list(vault, SUBJECT_INDEX_COLLECTION)\n for (const k of existing) {\n await adapter.delete(vault, SUBJECT_INDEX_COLLECTION, k)\n }\n\n // subjectId → refs, accumulated across all declared collections.\n const bySubject = new Map<string, SubjectRef[]>()\n for (const [collection, field] of Object.entries(subjects)) {\n const ids = await adapter.list(vault, collection)\n for (const id of ids) {\n if (id.startsWith('_')) continue\n const env = await adapter.get(vault, collection, id)\n if (!env || !env._data) continue // missing or tombstone\n const record = await decodeRecord(collection, id, env)\n if (record === null) continue\n const subjectValue = readDottedPath(record, field)\n if (subjectValue === undefined || subjectValue === null) continue\n const subjectId = coerceSubjectId(subjectValue)\n const list = bySubject.get(subjectId) ?? []\n list.push({ collection, id })\n bySubject.set(subjectId, list)\n }\n }\n\n let entries = 0\n for (const [subjectId, refs] of bySubject) {\n const key = await primarySubjectKey(getDEK, encrypted, subjectId)\n await writeRefs(adapter, vault, getDEK, encrypted, key, refs)\n entries++\n }\n return entries\n}\n\n/**\n * Coerce a read subject-field value to a stable string id. Primitives use\n * their natural string form; objects/arrays are JSON-stringified so structural\n * subjects still get a deterministic key (avoids the `[object Object]` trap).\n */\nexport function coerceSubjectId(value: unknown): string {\n if (typeof value === 'string') return value\n if (typeof value === 'number' || typeof value === 'boolean' || typeof value === 'bigint') {\n return String(value)\n }\n return JSON.stringify(value)\n}\n\n/** Read a (possibly dotted) field path from a plain record. */\nexport function readDottedPath(record: Record<string, unknown>, field: string): unknown {\n if (!field.includes('.')) return record[field]\n let cursor: unknown = record\n for (const segment of field.split('.')) {\n if (cursor === null || cursor === undefined) return undefined\n cursor = (cursor as Record<string, unknown>)[segment]\n }\n return cursor\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/with-party/team/managed-passphrase.ts"],"sourcesContent":["/**\n * Managed-passphrase mode — rubber-hose-resistant vaults.\n *\n * A vault mode where the passphrase is machine-generated and never\n * exposed to the user, sealed under a developer-provided\n * {@link SealingKeyProvider} (macOS Keychain, Windows Credential\n * Manager, libsecret, AWS KMS, …). The user has no secret to give\n * up to coercion — they can't reveal what they don't know.\n *\n * ## Components in this file\n *\n * - {@link SealingKeyProvider} — the interface concrete providers\n * implement. Provider implementations live OUTSIDE hub (per-\n * platform packages).\n * - {@link MemorySealingKeyProvider} — in-memory test provider; uses\n * a deterministic per-instance \"key\" so two providers with\n * different ids cannot unseal each other's outputs.\n * - {@link RecipientHint} — public material a sender uses to seal\n * plaintext for a specific recipient; published by\n * {@link RecipientSealer.publishRecipientHint} and transported\n * out-of-band to the sender before bundle writes.\n * - {@link RecipientSealer} — interface for asymmetric/granted\n * providers that support recipient-target sealing (RSA-OAEP,\n * cloud-KMS asymmetric, etc.); distinct from self-only\n * {@link SealingKeyProvider} (macOS Keychain, WebAuthn-PRF).\n * - {@link MemoryRecipientSealer} — in-process reference\n * implementation of both `RecipientSealer` and\n * `SealingKeyProvider` using real WebCrypto RSA-OAEP + AES-GCM;\n * safe for tests and same-process sender/recipient scenarios.\n * - {@link loadSealedPassphrase} / {@link saveSealedPassphrase} —\n * plaintext envelope storage at `_meta/sealed-passphrase`.\n * Mirrors the `_meta/handle` and `_meta/public-envelope` AES-\n * GCM-bypassed patterns. The sealing layer (provider's job)\n * is the security boundary; hub doesn't have a key to encrypt\n * with at this layer — that's the whole point of the design.\n * - {@link resolveManagedSecret} — orchestrates the \"generate +\n * seal + persist on first open; unseal on reopen\" flow.\n * Returns the plaintext passphrase string that the rest of the\n * `createNoydb` keyring path consumes.\n *\n * Deferred to follow-ups:\n * - Block `rotate-passphrase` policy gate under managed mode.\n * - Mandatory strong-recovery enforcement.\n * - Recovery flow under managed mode (generates fresh sealed phrase).\n *\n * @see https://github.com/vLannaAi/noy-db-docs/blob/main/content/docs/services/session-tiers.md → Managed-passphrase mode\n *\n * @module\n */\n\nimport type { NoydbStore, EncryptedEnvelope, RecipientSealer } from '../../kernel/types.js'\nimport { NOYDB_FORMAT_VERSION } from '../../kernel/types.js'\n\n/**\n * The contract concrete providers (per-platform key stores) implement\n * to seal and unseal a hub-generated random passphrase. The plaintext\n * passphrase NEVER leaves hub-controlled memory in unsealed form —\n * the provider receives the bytes, returns opaque sealed bytes, and\n * later reverses the operation. Hub treats the sealed bytes as\n * fully opaque.\n *\n * Implementations live OUTSIDE `@noy-db/hub` (separate packages\n * per the issue's \"Concrete providers (live outside hub)\" note):\n *\n * | Platform | Package (TBD) | Backing |\n * |---|---|---|\n * | macOS | `@noy-db/seal-macos-keychain` | Security.framework |\n * | Windows | `@noy-db/seal-wincred` | Credential Manager |\n * | Linux | `@noy-db/seal-libsecret` | libsecret / secret-service |\n * | Cloud / server | `@noy-db/seal-aws-kms` | AWS KMS Decrypt |\n */\nexport interface SealingKeyProvider {\n /**\n * Non-sensitive identifier disclosed in the persisted envelope.\n * Surfaced to consumers via `loadSealedPassphrase().providerId` so\n * a vault opened with the wrong provider class can detect the\n * mismatch and surface a clear error. NOT secret — fine to log.\n *\n * Suggested format: `<family>:<scope>` — e.g. `macos-keychain:com.acme.app`,\n * `aws-kms:arn:aws:kms:us-east-1:123:key/abc`. The hub never\n * parses this; it's purely audit metadata.\n */\n readonly id: string\n\n /** Seal raw passphrase bytes. Output bytes are opaque to hub. */\n seal(passphrase: Uint8Array): Promise<Uint8Array>\n\n /**\n * Reverse {@link seal}. MUST throw on tamper, wrong-provider, or\n * any other failure — hub treats a thrown error as \"this provider\n * cannot unlock this vault\" and surfaces it to the caller.\n */\n unseal(sealed: Uint8Array): Promise<Uint8Array>\n}\n\n/**\n * In-memory test provider. NOT secure — uses a deterministic\n * per-instance \"key\" (16-byte SHA-256 of `id`) XOR'd over the\n * passphrase plus a 4-byte provider-id fingerprint prefix. The XOR is\n * sufficient to make different `id` values produce mutually-unsealable\n * outputs (the contract tests for that), but offers ZERO real\n * confidentiality — never use outside tests.\n *\n * Replace with a real platform provider in production.\n */\nexport class MemorySealingKeyProvider implements SealingKeyProvider {\n readonly id: string\n private readonly fingerprint: Uint8Array\n private readonly keyBytes: Uint8Array\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n // Deterministic 4-byte fingerprint of the provider id, prepended\n // to every sealed output so we can detect \"wrong provider\" at\n // unseal time without leaking anything sensitive about either\n // provider's actual key material.\n const encoded = new TextEncoder().encode(opts.id)\n let h = 0\n for (let i = 0; i < encoded.length; i++) {\n h = (h * 31 + encoded[i]!) >>> 0\n }\n this.fingerprint = new Uint8Array([\n (h >>> 24) & 0xff, (h >>> 16) & 0xff, (h >>> 8) & 0xff, h & 0xff,\n ])\n // Deterministic 16-byte \"key\" derived from the id by repeating\n // the fingerprint with offsets. Good enough for the XOR-stream\n // test cipher; never confuse this with real key derivation.\n this.keyBytes = new Uint8Array(16)\n for (let i = 0; i < 16; i++) {\n this.keyBytes[i] = this.fingerprint[i % 4]! ^ (i * 17)\n }\n }\n\n async seal(passphrase: Uint8Array): Promise<Uint8Array> {\n const out = new Uint8Array(4 + passphrase.length)\n out.set(this.fingerprint, 0)\n for (let i = 0; i < passphrase.length; i++) {\n out[4 + i] = passphrase[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n\n async unseal(sealed: Uint8Array): Promise<Uint8Array> {\n if (sealed.length < 4) {\n throw new Error('MemorySealingKeyProvider: sealed input too short')\n }\n for (let i = 0; i < 4; i++) {\n if (sealed[i] !== this.fingerprint[i]) {\n throw new Error(\n `MemorySealingKeyProvider(\"${this.id}\"): provider-id mismatch on unseal `\n + '(sealed bytes were produced by a different provider)',\n )\n }\n }\n const body = sealed.subarray(4)\n const out = new Uint8Array(body.length)\n for (let i = 0; i < body.length; i++) {\n out[i] = body[i]! ^ this.keyBytes[i % 16]!\n }\n return out\n }\n}\n\n/**\n * Public material a sender uses to seal-for-this-recipient. Published by\n * a recipient's RecipientSealer; transported to the sender out-of-band\n * (email, S3, in-app message). The sender obtains the hint, supplies it\n * to writePod's sealedCredentials.perUser[userId].hint, and the\n * hub seals each user's credential against it. Per foundation §11.4.\n */\nexport type RecipientHint = {\n readonly v: 1\n /** Recipient's provider id; matches the SealedAutoUnlockEntry.pid they'll unseal under. */\n readonly pid: string\n /** Algorithm the sender uses to produce the seal. Slice 1 ships RSA-OAEP-SHA256 only. */\n readonly alg: 'rsa-oaep-sha256'\n /** Public material — alg-specific. For 'rsa-oaep-sha256': { publicKeyPem: string }. */\n readonly material: Readonly<Record<string, unknown>>\n}\n\n// Hoisted to kernel/types.ts (C3 — enclave self-containment: sealing.ts\n// consumes this as a spine-owned contract type). Re-exported here so existing\n// importers of this module are unaffected.\nexport type { RecipientSealer } from '../../kernel/types.js'\n\n/**\n * Shared RSA-OAEP-SHA256 + AES-GCM seal in the canonical recipient-target\n * TLV wire format. Mints a fresh 32-byte CEK, AES-GCM-encrypts `plaintext`\n * under it, RSA-OAEP-SHA256-wraps the CEK to `publicKeyPem`, and packs:\n *\n * byte 0 : version (0x01)\n * bytes 1..256 : RSA-OAEP-wrapped CEK (fixed 256 bytes at RSA-2048)\n * bytes 257..268: AES-GCM IV (12 bytes)\n * bytes 269.. : AES-GCM ciphertext ‖ 16-byte tag\n *\n * This is the single source of truth for the wire format — both\n * {@link MemoryRecipientSealer} and external sealers (e.g. `@noy-db/at-aws-kms`'s\n * asymmetric-KMS recipient sealer) call it so a blob sealed by one unseals\n * by the other. WebCrypto RSA-OAEP/SHA-256 here is wire-compatible with\n * AWS KMS `RSAES_OAEP_SHA_256` (both RSAES-OAEP, SHA-256 hash, MGF1-SHA256,\n * empty label).\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport async function sealRsaOaepTlv(plaintext: Uint8Array, publicKeyPem: string): Promise<Uint8Array> {\n // Parse PEM → SPKI bytes.\n const b64 = publicKeyPem.replace(/-----BEGIN PUBLIC KEY-----/, '').replace(/-----END PUBLIC KEY-----/, '').replace(/\\s+/g, '')\n const spki = base64ToBytes(b64)\n const recipientPub = await crypto.subtle.importKey(\n 'spki', spki as BufferSource,\n { name: 'RSA-OAEP', hash: 'SHA-256' },\n false, ['encrypt'],\n )\n // Mint fresh CEK + IV, AES-GCM encrypt plaintext.\n const cekBytes = crypto.getRandomValues(new Uint8Array(32))\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['encrypt'])\n const iv = crypto.getRandomValues(new Uint8Array(12))\n const ct = new Uint8Array(await crypto.subtle.encrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, plaintext as BufferSource))\n // RSA-OAEP-wrap the CEK bytes.\n const wrapped = new Uint8Array(await crypto.subtle.encrypt({ name: 'RSA-OAEP' }, recipientPub, cekBytes as BufferSource))\n cekBytes.fill(0)\n if (wrapped.length !== 256) {\n throw new Error(`sealRsaOaepTlv: expected 256-byte RSA-OAEP wrap, got ${wrapped.length}`)\n }\n // TLV layout.\n const out = new Uint8Array(1 + 256 + 12 + ct.length)\n out[0] = 0x01\n out.set(wrapped, 1)\n out.set(iv, 1 + 256)\n out.set(ct, 1 + 256 + 12)\n return out\n}\n\n/**\n * Parse a {@link sealRsaOaepTlv} blob into its three segments without\n * decrypting. The `wrapped` CEK is RSA-OAEP-SHA256 ciphertext over a\n * 32-byte CEK — the unwrap step is pluggable: {@link MemoryRecipientSealer}\n * decrypts it with a local RSA private key; `@noy-db/at-aws-kms` hands it to\n * KMS `Decrypt`. After unwrapping, pass the CEK + `iv` + `ct` to\n * {@link aesGcmOpen}.\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport function parseRsaOaepTlv(bytes: Uint8Array): { wrapped: Uint8Array; iv: Uint8Array; ct: Uint8Array } {\n if (bytes.length < 1 + 256 + 12 + 16) {\n throw new Error('parseRsaOaepTlv: sealed input too short')\n }\n if (bytes[0] !== 0x01) {\n throw new Error(`parseRsaOaepTlv: unknown TLV version ${bytes[0]}`)\n }\n return {\n wrapped: bytes.subarray(1, 1 + 256),\n iv: bytes.subarray(1 + 256, 1 + 256 + 12),\n ct: bytes.subarray(1 + 256 + 12),\n }\n}\n\n/**\n * AES-GCM-decrypt the `ct` segment of a {@link parseRsaOaepTlv} result under\n * the unwrapped 32-byte CEK and its `iv`. Throws on a bad tag (tamper) — the\n * same authenticated-decryption guarantee the TLV relies on.\n *\n * @public — re-exported from the hub barrel for external sealer packages.\n */\nexport async function aesGcmOpen(cekBytes: Uint8Array, iv: Uint8Array, ct: Uint8Array): Promise<Uint8Array> {\n const cek = await crypto.subtle.importKey('raw', cekBytes as BufferSource, 'AES-GCM', false, ['decrypt'])\n return new Uint8Array(await crypto.subtle.decrypt({ name: 'AES-GCM', iv: iv as BufferSource }, cek, ct as BufferSource))\n}\n\n/**\n * Reference implementation of `RecipientSealer` + `SealingKeyProvider`.\n * Uses WebCrypto RSA-OAEP-SHA256 (2048-bit) to wrap a fresh 32-byte\n * AES-GCM CEK, AES-GCM-encrypts plaintext under it, and packs the\n * result into a self-describing TLV:\n *\n * byte 0 : version (0x01)\n * bytes 1..256 : RSA-OAEP-wrapped CEK (fixed 256 bytes at RSA-2048)\n * bytes 257..268: AES-GCM IV (12 bytes)\n * bytes 269.. : AES-GCM ciphertext ‖ 16-byte tag\n *\n * Implements BOTH interfaces. `seal(plaintext)` (self-target) is just\n * `sealForRecipient(plaintext, this own hint)` — same TLV. Convenient\n * for tests where one provider plays both ends. Real cloud providers\n * (`at-aws-kms`, etc.) will pick their own internal layouts; the only\n * contract is round-trip identity.\n *\n * SAFE for production within its scope — the cryptography is real\n * (RSA-OAEP + AES-GCM via WebCrypto), but the keypair lives in-process\n * and is regenerated on every construction. Not suitable as a managed\n * keychain; use it for tests and for shipping bundles where the\n * recipient instance lives in the same process as the sender (rare).\n */\nexport class MemoryRecipientSealer implements SealingKeyProvider, RecipientSealer {\n readonly id: string\n private readonly keypair: Promise<CryptoKeyPair>\n\n constructor(opts: { id: string }) {\n this.id = opts.id\n this.keypair = crypto.subtle.generateKey(\n { name: 'RSA-OAEP', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256' },\n true,\n ['encrypt', 'decrypt'],\n )\n }\n\n async publishRecipientHint(): Promise<RecipientHint> {\n const { publicKey } = await this.keypair\n const spki = await crypto.subtle.exportKey('spki', publicKey)\n const pem = '-----BEGIN PUBLIC KEY-----\\n'\n + bytesToBase64(new Uint8Array(spki)).match(/.{1,64}/g)!.join('\\n')\n + '\\n-----END PUBLIC KEY-----\\n'\n return { v: 1, pid: this.id, alg: 'rsa-oaep-sha256', material: { publicKeyPem: pem } }\n }\n\n async sealForRecipient(plaintext: Uint8Array, hint: RecipientHint): Promise<Uint8Array> {\n if (hint.v !== 1) {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.v ${String(hint.v)} (expected 1)`)\n }\n if (hint.alg !== 'rsa-oaep-sha256') {\n throw new Error(`MemoryRecipientSealer.sealForRecipient: unsupported hint.alg '${String(hint.alg)}' (expected 'rsa-oaep-sha256')`)\n }\n const pem = hint.material['publicKeyPem']\n if (typeof pem !== 'string') {\n throw new Error('MemoryRecipientSealer.sealForRecipient: hint.material.publicKeyPem missing or not a string')\n }\n return sealRsaOaepTlv(plaintext, pem)\n }\n\n async seal(plaintext: Uint8Array): Promise<Uint8Array> {\n const hint = await this.publishRecipientHint()\n return this.sealForRecipient(plaintext, hint)\n }\n\n async unseal(bytes: Uint8Array): Promise<Uint8Array> {\n const { wrapped, iv, ct } = parseRsaOaepTlv(bytes)\n const { privateKey } = await this.keypair\n // Local RSA-OAEP-SHA256 unwrap of the CEK — the pluggable step external\n // sealers replace with KMS Decrypt.\n const cekBytes = new Uint8Array(await crypto.subtle.decrypt({ name: 'RSA-OAEP' }, privateKey, wrapped as BufferSource))\n const pt = await aesGcmOpen(cekBytes, iv, ct)\n cekBytes.fill(0)\n return pt\n }\n}\n\n// ─── Persisted envelope ────────────────────────────────────────────────\n\n/** Reserved id for the managed-passphrase envelope under `_meta`. */\nexport const SEALED_PASSPHRASE_RECORD_ID = 'sealed-passphrase' as const\n\n/** Plaintext payload stored inside the `_meta/sealed-passphrase` envelope. */\nexport interface SealedPassphrase {\n readonly _noydb_sealed: 1\n readonly providerId: string\n /** Sealed bytes. Base64-encoded on the wire; decoded on load. */\n readonly sealed: Uint8Array\n}\n\n/**\n * Wire-format envelope persisted at `_meta/sealed-passphrase` for\n * managed-mode vaults. The provider produces raw sealed bytes via\n * {@link SealingKeyProvider.seal}; this wrapper carries the dispatch\n * metadata hub needs to pick the right provider on the unseal path.\n *\n * Stability boundary: once shipped, the wire format only grows by\n * adding optional fields. See the at-* sealing dimension foundation\n * doc, §11.9.1.\n *\n * v1 shape (this release): `{ v: 1, _noydb_sealed: 1, pid, payload }`.\n *\n * Legacy shape (earlier releases): `{ _noydb_sealed: 1, providerId, sealed }`\n * — accepted on read for backwards compatibility; never produced on\n * write going forward.\n */\nexport interface SealedEnvelope {\n /** Envelope schema version. v1 is the current shape. */\n readonly v: 1\n /** Magic marker for forensics + legacy-shape detection. */\n readonly _noydb_sealed: 1\n /** Matches the producing provider's `.id`. Dispatch key on unseal. */\n readonly pid: string\n /** Sealed bytes from the provider, base64-encoded on the wire. */\n readonly payload: string\n}\n\nfunction bytesToBase64(bytes: Uint8Array): string {\n let binary = ''\n for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!)\n return btoa(binary)\n}\n\nfunction base64ToBytes(b64: string): Uint8Array {\n const binary = atob(b64)\n const out = new Uint8Array(binary.length)\n for (let i = 0; i < binary.length; i++) out[i] = binary.charCodeAt(i)\n return out\n}\n\n/**\n * Parse a `_meta/sealed-passphrase` `_data` JSON string into the\n * in-memory {@link SealedPassphrase} representation. Accepts both:\n *\n * 1. v1 wire format `{ v: 1, _noydb_sealed: 1, pid, payload }` —\n * the current shape.\n * 2. Legacy wire format `{ _noydb_sealed: 1, providerId, sealed }` —\n * read-only; never written\n * going forward.\n *\n * Returns `undefined` for any input that doesn't match either shape,\n * so callers can fall back to \"no managed-mode envelope present.\"\n *\n * @internal — exported only for the migration safety-net test suite.\n */\nexport function parseSealedEnvelope(raw: unknown): SealedPassphrase | undefined {\n if (typeof raw !== 'object' || raw === null) return undefined\n const r = raw as Record<string, unknown>\n if (r._noydb_sealed !== 1) return undefined\n\n // v1 shape — preferred.\n if (\n r.v === 1\n && typeof r.pid === 'string'\n && typeof r.payload === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.pid,\n sealed: base64ToBytes(r.payload),\n }\n }\n\n // Legacy shape — earlier releases. Accept on read for compat.\n if (\n typeof r.providerId === 'string'\n && typeof r.sealed === 'string'\n ) {\n return {\n _noydb_sealed: 1,\n providerId: r.providerId,\n sealed: base64ToBytes(r.sealed),\n }\n }\n\n return undefined\n}\n\nexport async function saveSealedPassphrase(\n store: NoydbStore,\n vault: string,\n payload: { readonly providerId: string; readonly sealed: Uint8Array },\n): Promise<void> {\n const persisted: SealedEnvelope = {\n v: 1,\n _noydb_sealed: 1,\n pid: payload.providerId,\n payload: bytesToBase64(payload.sealed),\n }\n const prior = await store.get(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID)\n const env: EncryptedEnvelope = {\n _noydb: NOYDB_FORMAT_VERSION,\n _v: (prior?._v ?? 0) + 1,\n _ts: new Date().toISOString(),\n // AES-GCM bypassed — the sealing layer is the security boundary.\n _iv: '',\n _data: JSON.stringify(persisted),\n }\n await store.put(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID, env)\n}\n\nexport async function loadSealedPassphrase(\n store: NoydbStore,\n vault: string,\n): Promise<SealedPassphrase | undefined> {\n const envelope = await store.get(vault, '_meta', SEALED_PASSPHRASE_RECORD_ID)\n if (!envelope) return undefined\n try {\n return parseSealedEnvelope(JSON.parse(envelope._data))\n } catch {\n return undefined\n }\n}\n\n// ─── createNoydb orchestration ─────────────────────────────────────────\n\n/**\n * Resolve the effective plaintext passphrase string for a managed-mode\n * vault. Two paths:\n *\n * 1. **First open (no envelope persisted):** generate a 256-bit random\n * via `crypto.getRandomValues`, base64-encode for use as a\n * passphrase string, seal the underlying bytes under the\n * provider, persist `_meta/sealed-passphrase`, return the\n * base64 string.\n *\n * 2. **Reopen (envelope exists):** read + unseal + decode → return.\n * A different provider whose `seal` output disagrees on the\n * stored bytes throws here, surfaced as a clear error.\n *\n * The returned string is the same shape that `secret:` would take in\n * standard mode — the rest of the keyring path consumes it\n * unchanged.\n *\n * @internal — called from `createNoydb` / `getKeyringInternal`.\n */\nexport async function resolveManagedSecret(\n store: NoydbStore,\n vault: string,\n provider: SealingKeyProvider,\n): Promise<string> {\n const existing = await loadSealedPassphrase(store, vault)\n if (existing) {\n if (existing.providerId !== provider.id) {\n throw new Error(\n `Managed-mode vault \"${vault}\" was sealed under provider id `\n + `\"${existing.providerId}\" but the current SealingKeyProvider is `\n + `\"${provider.id}\". Pass the same provider that originally enrolled `\n + 'the vault, or treat this as a fresh enrollment and clear '\n + '`_meta/sealed-passphrase` first.',\n )\n }\n const plaintext = await provider.unseal(existing.sealed)\n return bytesToBase64(plaintext)\n }\n\n // First open: mint a 256-bit random, seal, persist.\n const random = new Uint8Array(32)\n globalThis.crypto.getRandomValues(random)\n const sealed = await provider.seal(random)\n await saveSealedPassphrase(store, vault, { providerId: provider.id, sealed })\n return bytesToBase64(random)\n}\n"],"mappings":"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","names":[]}
@@ -1,163 +0,0 @@
1
- import {
2
- diff
3
- } from "./chunk-SVLR4POP.js";
4
-
5
- // src/with-cargo/vault-diff.ts
6
- async function diffVault(vault, candidate, options = {}) {
7
- const idKey = options.idKey ?? "id";
8
- const filter = options.collections ? new Set(options.collections) : null;
9
- const compareFn = options.compareFn ?? ((a, b) => diff(a, b).length === 0);
10
- const live = /* @__PURE__ */ new Map();
11
- for await (const chunk of vault.exportStream({ granularity: "collection" })) {
12
- if (filter && !filter.has(chunk.collection)) continue;
13
- const collection = live.get(chunk.collection) ?? /* @__PURE__ */ new Map();
14
- for (const record of chunk.records) {
15
- const id = readIdField(record, idKey);
16
- if (!id) continue;
17
- collection.set(id, record);
18
- }
19
- live.set(chunk.collection, collection);
20
- }
21
- const cand = await normaliseCandidate(candidate, idKey, filter);
22
- const added = [];
23
- const modified = [];
24
- const deleted = [];
25
- const unchanged = options.includeUnchanged ? [] : void 0;
26
- const collectionNames = /* @__PURE__ */ new Set([...live.keys(), ...cand.keys()]);
27
- for (const collection of [...collectionNames].sort()) {
28
- const liveColl = live.get(collection) ?? /* @__PURE__ */ new Map();
29
- const candColl = cand.get(collection) ?? /* @__PURE__ */ new Map();
30
- const allIds = /* @__PURE__ */ new Set([...liveColl.keys(), ...candColl.keys()]);
31
- for (const id of [...allIds].sort()) {
32
- const before = liveColl.get(id);
33
- const after = candColl.get(id);
34
- if (before === void 0 && after !== void 0) {
35
- added.push({ collection, id, record: after });
36
- } else if (before !== void 0 && after === void 0) {
37
- const metadata = options.includeMetadata ? await vault.collection(collection).getMetadata(id) ?? void 0 : void 0;
38
- deleted.push({ collection, id, record: before, ...metadata !== void 0 ? { metadata } : {} });
39
- } else if (before !== void 0 && after !== void 0) {
40
- if (compareFn(before, after)) {
41
- unchanged?.push({ collection, id, record: after });
42
- } else {
43
- const metadata = options.includeMetadata ? await vault.collection(collection).getMetadata(id) ?? void 0 : void 0;
44
- const fieldDiffs = diff(before, after);
45
- const fieldsChanged = uniqueTopLevelKeys(fieldDiffs);
46
- modified.push({
47
- collection,
48
- id,
49
- record: after,
50
- before,
51
- fieldsChanged,
52
- fieldDiffs,
53
- ...metadata !== void 0 ? { metadata } : {}
54
- });
55
- }
56
- }
57
- }
58
- }
59
- const summary = {
60
- add: added.length,
61
- modify: modified.length,
62
- delete: deleted.length,
63
- total: added.length + modified.length + deleted.length
64
- };
65
- return {
66
- added,
67
- modified,
68
- deleted,
69
- unchanged,
70
- summary,
71
- format(opts) {
72
- return formatDiff(opts?.detail ?? "full", { added, modified, deleted, summary });
73
- }
74
- };
75
- }
76
- async function normaliseCandidate(candidate, idKey, filter) {
77
- const out = /* @__PURE__ */ new Map();
78
- if (typeof candidate === "object" && candidate !== null && "exportStream" in candidate && typeof candidate.exportStream === "function") {
79
- for await (const chunk of candidate.exportStream({ granularity: "collection" })) {
80
- if (filter && !filter.has(chunk.collection)) continue;
81
- const collection = out.get(chunk.collection) ?? /* @__PURE__ */ new Map();
82
- for (const record of chunk.records) {
83
- const id = readIdField(record, idKey);
84
- if (!id) continue;
85
- collection.set(id, record);
86
- }
87
- out.set(chunk.collection, collection);
88
- }
89
- return out;
90
- }
91
- if (typeof candidate === "string") {
92
- let parsed;
93
- try {
94
- parsed = JSON.parse(candidate);
95
- } catch (err) {
96
- throw new Error(
97
- `diffVault: candidate string is not valid JSON (${err.message})`
98
- );
99
- }
100
- return collectionsFromObject(parsed, idKey, filter);
101
- }
102
- return collectionsFromObject(candidate, idKey, filter);
103
- }
104
- function collectionsFromObject(raw, idKey, filter) {
105
- const out = /* @__PURE__ */ new Map();
106
- if (raw === null || typeof raw !== "object") {
107
- throw new Error("diffVault: candidate must be a Vault, an object, or a JSON string");
108
- }
109
- for (const [key, value] of Object.entries(raw)) {
110
- if (key.startsWith("_")) continue;
111
- if (filter && !filter.has(key)) continue;
112
- if (!Array.isArray(value)) continue;
113
- const collection = /* @__PURE__ */ new Map();
114
- for (const record of value) {
115
- if (record === null || typeof record !== "object") continue;
116
- const id = readIdField(record, idKey);
117
- if (!id) continue;
118
- collection.set(id, record);
119
- }
120
- out.set(key, collection);
121
- }
122
- return out;
123
- }
124
- function uniqueTopLevelKeys(diffs) {
125
- const keys = /* @__PURE__ */ new Set();
126
- for (const d of diffs) {
127
- const m = /^[^.[]+/.exec(d.path);
128
- if (m) keys.add(m[0]);
129
- }
130
- return [...keys];
131
- }
132
- function readIdField(record, idKey) {
133
- if (record === null || typeof record !== "object") return "";
134
- const v = record[idKey];
135
- if (typeof v === "string") return v;
136
- if (typeof v === "number" && Number.isFinite(v)) return String(v);
137
- return "";
138
- }
139
- function formatDiff(detail, b) {
140
- const head = `${b.summary.add} added \xB7 ${b.summary.modify} modified \xB7 ${b.summary.delete} deleted`;
141
- if (detail === "count") return head;
142
- if (b.summary.total === 0) return head + "\n(no changes)";
143
- if (detail === "one-line") return head;
144
- const rows = [head, ""];
145
- for (const e of b.added) rows.push(`${e.collection}/${e.id} added`);
146
- for (const e of b.modified) {
147
- const fields = e.fieldDiffs.map((f) => `${f.path}: ${shortJSON(f.from)} \u2192 ${shortJSON(f.to)}`).join(", ");
148
- rows.push(`${e.collection}/${e.id} modified ${fields}`);
149
- }
150
- for (const e of b.deleted) rows.push(`${e.collection}/${e.id} deleted`);
151
- return rows.join("\n");
152
- }
153
- function shortJSON(value) {
154
- if (value === void 0) return "undefined";
155
- const s = JSON.stringify(value);
156
- if (typeof s !== "string") return "<unrepresentable>";
157
- return s.length > 60 ? s.slice(0, 57) + "..." : s;
158
- }
159
-
160
- export {
161
- diffVault
162
- };
163
- //# sourceMappingURL=chunk-LB7FD65R.js.map