@noy-db/hub 0.2.0-pre.2 → 0.2.0-pre.21

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 (368) hide show
  1. package/README.md +126 -0
  2. package/dist/aggregate/index.cjs +643 -37
  3. package/dist/aggregate/index.cjs.map +1 -1
  4. package/dist/aggregate/index.d.cts +3 -2
  5. package/dist/aggregate/index.d.ts +3 -2
  6. package/dist/aggregate/index.js +9 -8
  7. package/dist/aggregate/index.js.map +1 -1
  8. package/dist/attestation/index.cjs.map +1 -1
  9. package/dist/attestation/index.d.cts +7 -5
  10. package/dist/attestation/index.d.ts +7 -5
  11. package/dist/attestation/index.js +6 -6
  12. package/dist/blobs/index.cjs +509 -22
  13. package/dist/blobs/index.cjs.map +1 -1
  14. package/dist/blobs/index.d.cts +9 -7
  15. package/dist/blobs/index.d.ts +9 -7
  16. package/dist/blobs/index.js +11 -6
  17. package/dist/blobs/index.js.map +1 -1
  18. package/dist/bundle/index.cjs +7886 -841
  19. package/dist/bundle/index.cjs.map +1 -1
  20. package/dist/bundle/index.d.cts +20 -18
  21. package/dist/bundle/index.d.ts +20 -18
  22. package/dist/bundle/index.js +24 -13
  23. package/dist/bundle/index.js.map +1 -1
  24. package/dist/{chunk-PFSNOPBQ.js → chunk-2XA2ZML4.js} +31 -3
  25. package/dist/chunk-2XA2ZML4.js.map +1 -0
  26. package/dist/{chunk-2PAQNPE3.js → chunk-37VGJM3T.js} +37 -2
  27. package/dist/chunk-37VGJM3T.js.map +1 -0
  28. package/dist/{chunk-7BRE6EUA.js → chunk-3HNKR65T.js} +4 -4
  29. package/dist/chunk-3HNKR65T.js.map +1 -0
  30. package/dist/{chunk-Y2RKOPNC.js → chunk-5YTXYPES.js} +46 -10
  31. package/dist/chunk-5YTXYPES.js.map +1 -0
  32. package/dist/{chunk-OVZDFEOR.js → chunk-6QAZ5O6X.js} +2 -2
  33. package/dist/chunk-6QAZ5O6X.js.map +1 -0
  34. package/dist/{chunk-RTZVQAJ7.js → chunk-6QE4DUYC.js} +19 -4
  35. package/dist/chunk-6QE4DUYC.js.map +1 -0
  36. package/dist/{chunk-7Q5PLD5C.js → chunk-7MRT7EPB.js} +3 -3
  37. package/dist/{chunk-E535SAN4.js → chunk-7PH4OPBZ.js} +4258 -520
  38. package/dist/chunk-7PH4OPBZ.js.map +1 -0
  39. package/dist/{chunk-PEULZC6M.js → chunk-A3JMGXPG.js} +8 -1
  40. package/dist/chunk-A3JMGXPG.js.map +1 -0
  41. package/dist/{chunk-UMLVJTYV.js → chunk-ADB7GPM3.js} +7 -4
  42. package/dist/chunk-ADB7GPM3.js.map +1 -0
  43. package/dist/{chunk-G6FRSBKK.js → chunk-AI4USDRI.js} +4 -4
  44. package/dist/chunk-BZW5IL43.js +151 -0
  45. package/dist/chunk-BZW5IL43.js.map +1 -0
  46. package/dist/chunk-C2RJVZZL.js +123 -0
  47. package/dist/chunk-C2RJVZZL.js.map +1 -0
  48. package/dist/{chunk-UND4XIB6.js → chunk-C6W5KVDV.js} +52 -38
  49. package/dist/chunk-C6W5KVDV.js.map +1 -0
  50. package/dist/chunk-CQYEDODS.js +125 -0
  51. package/dist/chunk-CQYEDODS.js.map +1 -0
  52. package/dist/{chunk-NWZ3I6R6.js → chunk-EYK72OTL.js} +5 -5
  53. package/dist/{chunk-7BUTTVMR.js → chunk-F5GWNSE2.js} +2 -2
  54. package/dist/{chunk-AHPFONIL.js → chunk-F5ILTHMU.js} +5 -5
  55. package/dist/{chunk-Q6W2CMEJ.js → chunk-FRRJIUSI.js} +18 -5
  56. package/dist/chunk-FRRJIUSI.js.map +1 -0
  57. package/dist/{chunk-YMYK7US4.js → chunk-GJTKMME7.js} +2 -2
  58. package/dist/chunk-GJTKMME7.js.map +1 -0
  59. package/dist/{chunk-EUYOGYGV.js → chunk-HYJMAV53.js} +6 -6
  60. package/dist/chunk-HYJMAV53.js.map +1 -0
  61. package/dist/{chunk-QPEXPHJR.js → chunk-I3IYTUUI.js} +4 -4
  62. package/dist/{chunk-3QAKZ37R.js → chunk-IVZWHIEK.js} +5 -5
  63. package/dist/{chunk-PLI5TV7N.js → chunk-IW4L4X65.js} +2 -2
  64. package/dist/chunk-IW4L4X65.js.map +1 -0
  65. package/dist/{chunk-3Z2TPHC4.js → chunk-IY24WS2P.js} +69 -5
  66. package/dist/chunk-IY24WS2P.js.map +1 -0
  67. package/dist/{chunk-HXJXPZRE.js → chunk-J6RGRZOY.js} +10 -3
  68. package/dist/chunk-J6RGRZOY.js.map +1 -0
  69. package/dist/{chunk-3S4BJX25.js → chunk-JBBWALNI.js} +2 -2
  70. package/dist/chunk-JBBWALNI.js.map +1 -0
  71. package/dist/{chunk-7Z23ZFLV.js → chunk-JDCPRJVS.js} +5 -5
  72. package/dist/chunk-JDCPRJVS.js.map +1 -0
  73. package/dist/{chunk-243PNUA6.js → chunk-JOK73NDT.js} +3 -3
  74. package/dist/chunk-JTI57WRT.js +164 -0
  75. package/dist/chunk-JTI57WRT.js.map +1 -0
  76. package/dist/{chunk-VRBCTEKQ.js → chunk-JYNH4FIM.js} +233 -11
  77. package/dist/chunk-JYNH4FIM.js.map +1 -0
  78. package/dist/{chunk-TBKOGSYR.js → chunk-KOAJ3TZM.js} +27 -5
  79. package/dist/chunk-KOAJ3TZM.js.map +1 -0
  80. package/dist/{chunk-YTXSFG3C.js → chunk-MBXKRHSS.js} +50 -20
  81. package/dist/chunk-MBXKRHSS.js.map +1 -0
  82. package/dist/{chunk-MUWOSVEP.js → chunk-NSXNXLYM.js} +10 -2
  83. package/dist/chunk-NSXNXLYM.js.map +1 -0
  84. package/dist/{chunk-J4KLMEUL.js → chunk-NV4IHBZS.js} +664 -51
  85. package/dist/chunk-NV4IHBZS.js.map +1 -0
  86. package/dist/{chunk-LRAZDV5X.js → chunk-O5XKZCUD.js} +31 -8
  87. package/dist/chunk-O5XKZCUD.js.map +1 -0
  88. package/dist/{chunk-W3XXT26A.js → chunk-OTWT6BAJ.js} +358 -3
  89. package/dist/chunk-OTWT6BAJ.js.map +1 -0
  90. package/dist/{chunk-XG3PTSCD.js → chunk-PDVP3C2I.js} +1 -1
  91. package/dist/chunk-PDVP3C2I.js.map +1 -0
  92. package/dist/{chunk-GIV6DWBG.js → chunk-S45MDEEF.js} +44 -5
  93. package/dist/chunk-S45MDEEF.js.map +1 -0
  94. package/dist/{chunk-VK5EER6C.js → chunk-SQKAECUL.js} +2 -2
  95. package/dist/{chunk-FAQVNJD4.js → chunk-SQOK5UM6.js} +12 -2
  96. package/dist/{chunk-FAQVNJD4.js.map → chunk-SQOK5UM6.js.map} +1 -1
  97. package/dist/chunk-STNPB3UM.js +9 -0
  98. package/dist/chunk-STNPB3UM.js.map +1 -0
  99. package/dist/{chunk-YS3POABP.js → chunk-TA6HPKWQ.js} +1 -1
  100. package/dist/chunk-TA6HPKWQ.js.map +1 -0
  101. package/dist/{chunk-4HIL6AHQ.js → chunk-TAMRU7A2.js} +4 -4
  102. package/dist/{chunk-QXQRKXCU.js → chunk-TGIJTNM3.js} +2 -2
  103. package/dist/chunk-TNH5SLCD.js +361 -0
  104. package/dist/chunk-TNH5SLCD.js.map +1 -0
  105. package/dist/{chunk-VPSUZLOJ.js → chunk-TYMDCIQM.js} +31 -5
  106. package/dist/chunk-TYMDCIQM.js.map +1 -0
  107. package/dist/chunk-U2XSUCDF.js +524 -0
  108. package/dist/chunk-U2XSUCDF.js.map +1 -0
  109. package/dist/{chunk-3Y53S2SA.js → chunk-UU6M64HI.js} +4 -4
  110. package/dist/{chunk-VCGTOS2A.js → chunk-WE2BUQD2.js} +3 -3
  111. package/dist/chunk-WE2BUQD2.js.map +1 -0
  112. package/dist/{chunk-JYQTXEIO.js → chunk-WWVJXBOT.js} +449 -29
  113. package/dist/chunk-WWVJXBOT.js.map +1 -0
  114. package/dist/chunk-YPIOFSN3.js +129 -0
  115. package/dist/chunk-YPIOFSN3.js.map +1 -0
  116. package/dist/chunk-ZC7J6ZYV.js +7 -0
  117. package/dist/chunk-ZC7J6ZYV.js.map +1 -0
  118. package/dist/{chunk-5ZGZ6HIZ.js → chunk-ZONKSLF2.js} +30 -7
  119. package/dist/chunk-ZONKSLF2.js.map +1 -0
  120. package/dist/consent/index.cjs.map +1 -1
  121. package/dist/consent/index.d.cts +8 -6
  122. package/dist/consent/index.d.ts +8 -6
  123. package/dist/consent/index.js +3 -3
  124. package/dist/{crypto-5ZDIY3NG.js → crypto-456N7UVX.js} +7 -3
  125. package/dist/{delegation-QYXZW25W.js → delegation-DP4COTXB.js} +5 -5
  126. package/dist/derivations/index.cjs +124 -6
  127. package/dist/derivations/index.cjs.map +1 -1
  128. package/dist/derivations/index.d.cts +11 -9
  129. package/dist/derivations/index.d.ts +11 -9
  130. package/dist/derivations/index.js +8 -6
  131. package/dist/{dev-unlock-DQCNDfFp.d.cts → dev-unlock-CY0HIZA0.d.cts} +1 -1
  132. package/dist/{dev-unlock-utkybTKb.d.ts → dev-unlock-CpKSkl2c.d.ts} +1 -1
  133. package/dist/discriminant-BN9REW3o.d.cts +60 -0
  134. package/dist/discriminant-BN9REW3o.d.ts +60 -0
  135. package/dist/errors-Dkc_fi-S.d.cts +1467 -0
  136. package/dist/errors-Dkc_fi-S.d.ts +1467 -0
  137. package/dist/executor-4IEW4KG5.js +8 -0
  138. package/dist/executor-KYJCJCIN.js +12 -0
  139. package/dist/executor-W7VIBOBZ.js +8 -0
  140. package/dist/{fanout-sidecar-VJ52RIEY.js → fanout-sidecar-YXNAEZ33.js} +2 -2
  141. package/dist/fanout-sidecar-YXNAEZ33.js.map +1 -0
  142. package/dist/forget/index.cjs +43 -0
  143. package/dist/forget/index.cjs.map +1 -0
  144. package/dist/forget/index.d.cts +1 -0
  145. package/dist/forget/index.d.ts +1 -0
  146. package/dist/forget/index.js +14 -0
  147. package/dist/guards/index.cjs +144 -4
  148. package/dist/guards/index.cjs.map +1 -1
  149. package/dist/guards/index.d.cts +16 -8
  150. package/dist/guards/index.d.ts +16 -8
  151. package/dist/guards/index.js +13 -7
  152. package/dist/{hash-jDowCrK2.d.cts → hash-BSd0-_L8.d.cts} +1 -1
  153. package/dist/{hash-DcoYWfJ_.d.ts → hash-BnBQx39y.d.ts} +1 -1
  154. package/dist/history/index.cjs +28 -5
  155. package/dist/history/index.cjs.map +1 -1
  156. package/dist/history/index.d.cts +9 -7
  157. package/dist/history/index.d.ts +9 -7
  158. package/dist/history/index.js +9 -7
  159. package/dist/history/index.js.map +1 -1
  160. package/dist/i18n/index.cjs +356 -26
  161. package/dist/i18n/index.cjs.map +1 -1
  162. package/dist/i18n/index.d.cts +8 -6
  163. package/dist/i18n/index.d.ts +8 -6
  164. package/dist/i18n/index.js +36 -15
  165. package/dist/i18n/index.js.map +1 -1
  166. package/dist/index-BMmajblo.d.cts +362 -0
  167. package/dist/index-BMmajblo.d.ts +362 -0
  168. package/dist/{index-BCKdioeh.d.ts → index-Bm9hIY7t.d.ts} +169 -1127
  169. package/dist/{index-BMjrzNZr.d.cts → index-tZqVB9g5.d.cts} +169 -1127
  170. package/dist/index.cjs +10286 -2168
  171. package/dist/index.cjs.map +1 -1
  172. package/dist/index.d.cts +258 -23
  173. package/dist/index.d.ts +258 -23
  174. package/dist/index.js +443 -110
  175. package/dist/index.js.map +1 -1
  176. package/dist/indexing/index.cjs +97 -32
  177. package/dist/indexing/index.cjs.map +1 -1
  178. package/dist/indexing/index.d.cts +3 -3
  179. package/dist/indexing/index.d.ts +3 -3
  180. package/dist/indexing/index.js +4 -4
  181. package/dist/issue-JXC6T2QR.js +12 -0
  182. package/dist/{lazy-builder-Rpd-V3jP.d.ts → lazy-builder-ChSqcF5t.d.ts} +2 -2
  183. package/dist/{lazy-builder-C-rPfWG0.d.cts → lazy-builder-eYZzLEL1.d.cts} +2 -2
  184. package/dist/{ledger-3IU5GMXA.js → ledger-I7JUYP4L.js} +6 -6
  185. package/dist/materialized-views/index.cjs +687 -13
  186. package/dist/materialized-views/index.cjs.map +1 -1
  187. package/dist/materialized-views/index.d.cts +23 -20
  188. package/dist/materialized-views/index.d.ts +23 -20
  189. package/dist/materialized-views/index.js +8 -7
  190. package/dist/mime-magic-BnJCGJzB.d.cts +103 -0
  191. package/dist/mime-magic-CjSyakO4.d.ts +103 -0
  192. package/dist/noydb-ZZCRF6TE.js +38 -0
  193. package/dist/overlay-views/index.cjs +58 -18
  194. package/dist/overlay-views/index.cjs.map +1 -1
  195. package/dist/overlay-views/index.d.cts +32 -12
  196. package/dist/overlay-views/index.d.ts +32 -12
  197. package/dist/overlay-views/index.js +6 -6
  198. package/dist/periods/index.cjs.map +1 -1
  199. package/dist/periods/index.d.cts +8 -6
  200. package/dist/periods/index.d.ts +8 -6
  201. package/dist/periods/index.js +6 -6
  202. package/dist/{predicate-Dnu81tsS.d.cts → predicate-BmhBSPCH.d.cts} +87 -5
  203. package/dist/{predicate-Dnu81tsS.d.ts → predicate-BmhBSPCH.d.ts} +87 -5
  204. package/dist/{public-envelope-U3CMEOMV.js → public-envelope-5XRTUNKF.js} +4 -4
  205. package/dist/query/index.cjs +1438 -130
  206. package/dist/query/index.cjs.map +1 -1
  207. package/dist/query/index.d.cts +4 -3
  208. package/dist/query/index.d.ts +4 -3
  209. package/dist/query/index.js +13 -6
  210. package/dist/read-only-facade-EX6WZZBP.js +7 -0
  211. package/dist/registry-ATRHOG5B.js +8 -0
  212. package/dist/registry-DKEXOJVO.js +7 -0
  213. package/dist/registry-LEHB26TY.js +8 -0
  214. package/dist/{registry-3ALP62P6.js → registry-NWHOLD5M.js} +3 -3
  215. package/dist/{revoke-KY2GB4KP.js → revoke-5IEK22KT.js} +6 -6
  216. package/dist/sealed-record/index.cjs +139 -0
  217. package/dist/sealed-record/index.cjs.map +1 -0
  218. package/dist/sealed-record/index.d.cts +123 -0
  219. package/dist/sealed-record/index.d.ts +123 -0
  220. package/dist/sealed-record/index.js +42 -0
  221. package/dist/sealed-record/index.js.map +1 -0
  222. package/dist/session/index.cjs.map +1 -1
  223. package/dist/session/index.d.cts +9 -7
  224. package/dist/session/index.d.ts +9 -7
  225. package/dist/session/index.js +3 -3
  226. package/dist/shadow/index.cjs.map +1 -1
  227. package/dist/shadow/index.d.cts +8 -6
  228. package/dist/shadow/index.d.ts +8 -6
  229. package/dist/shadow/index.js +2 -2
  230. package/dist/{signer-GRI5TZKH.js → signer-I6YARZQA.js} +5 -5
  231. package/dist/snapshots/index.cjs +937 -0
  232. package/dist/snapshots/index.cjs.map +1 -0
  233. package/dist/snapshots/index.d.cts +30 -0
  234. package/dist/snapshots/index.d.ts +30 -0
  235. package/dist/snapshots/index.js +152 -0
  236. package/dist/snapshots/index.js.map +1 -0
  237. package/dist/{stale-OTOF3FH7.js → stale-CPESGAPL.js} +2 -2
  238. package/dist/stale-CPESGAPL.js.map +1 -0
  239. package/dist/state-vault-JR3CFGNP.js +14 -0
  240. package/dist/state-vault-JR3CFGNP.js.map +1 -0
  241. package/dist/store/index.cjs +8 -0
  242. package/dist/store/index.cjs.map +1 -1
  243. package/dist/store/index.d.cts +15 -6
  244. package/dist/store/index.d.ts +15 -6
  245. package/dist/store/index.js +2 -2
  246. package/dist/{strategy-DSTrsZ8t.d.ts → strategy-54eIwox5.d.ts} +456 -7
  247. package/dist/{strategy-DSTrsZ8t.d.cts → strategy-WtB-jXYv.d.cts} +456 -7
  248. package/dist/sync/index.cjs.map +1 -1
  249. package/dist/sync/index.d.cts +7 -5
  250. package/dist/sync/index.d.ts +7 -5
  251. package/dist/sync/index.js +4 -4
  252. package/dist/team/index.cjs +1 -1
  253. package/dist/team/index.cjs.map +1 -1
  254. package/dist/team/index.d.cts +8 -6
  255. package/dist/team/index.d.ts +8 -6
  256. package/dist/team/index.js +8 -8
  257. package/dist/transition-guard-D4bfIAiW.d.ts +165 -0
  258. package/dist/transition-guard-Dmpqzg-_.d.cts +165 -0
  259. package/dist/tx/index.cjs +155 -5
  260. package/dist/tx/index.cjs.map +1 -1
  261. package/dist/tx/index.d.cts +27 -9
  262. package/dist/tx/index.d.ts +27 -9
  263. package/dist/tx/index.js +61 -4
  264. package/dist/tx/index.js.map +1 -1
  265. package/dist/{types-BoFFiskX.d.ts → types-DLfWFr6U.d.ts} +3997 -1262
  266. package/dist/{types-DJG8HG6F.d.cts → types-DyOI6XZ_.d.cts} +3997 -1262
  267. package/dist/{ulid-BmBgooGm.d.ts → ulid-B2L_aqVA.d.ts} +19 -19
  268. package/dist/{ulid-C7ms9oli.d.cts → ulid-LaxfH2tK.d.cts} +19 -19
  269. package/dist/util/index.cjs +7 -0
  270. package/dist/util/index.cjs.map +1 -1
  271. package/dist/util/index.d.cts +2 -0
  272. package/dist/util/index.d.ts +2 -0
  273. package/dist/util/index.js +5 -1
  274. package/dist/util/index.js.map +1 -1
  275. package/dist/vault-group-BB246VIM.js +804 -0
  276. package/dist/vault-group-BB246VIM.js.map +1 -0
  277. package/dist/{with-materialized-view-CqnRwI2S.d.ts → with-materialized-view-CeZYGJVf.d.cts} +2 -2
  278. package/dist/{with-materialized-view-BbEPFIIJ.d.cts → with-materialized-view-DNULSxoP.d.ts} +2 -2
  279. package/dist/{with-overlayed-view-Ct1fSJt-.d.ts → with-overlayed-view-C9joG7UZ.d.ts} +2 -2
  280. package/dist/{with-overlayed-view-bwlmmFjx.d.cts → with-overlayed-view-kdcPGHih.d.cts} +2 -2
  281. package/dist/with-rollup-DJDbrxjf.d.ts +47 -0
  282. package/dist/with-rollup-s58XAeWO.d.cts +47 -0
  283. package/package.json +35 -4
  284. package/dist/chunk-2PAQNPE3.js.map +0 -1
  285. package/dist/chunk-3S4BJX25.js.map +0 -1
  286. package/dist/chunk-3XHOCQK4.js +0 -118
  287. package/dist/chunk-3XHOCQK4.js.map +0 -1
  288. package/dist/chunk-3Z2TPHC4.js.map +0 -1
  289. package/dist/chunk-5ZGZ6HIZ.js.map +0 -1
  290. package/dist/chunk-7BRE6EUA.js.map +0 -1
  291. package/dist/chunk-7Z23ZFLV.js.map +0 -1
  292. package/dist/chunk-CXSCDO5T.js +0 -51
  293. package/dist/chunk-CXSCDO5T.js.map +0 -1
  294. package/dist/chunk-E535SAN4.js.map +0 -1
  295. package/dist/chunk-EUYOGYGV.js.map +0 -1
  296. package/dist/chunk-GIV6DWBG.js.map +0 -1
  297. package/dist/chunk-HXJXPZRE.js.map +0 -1
  298. package/dist/chunk-J4KLMEUL.js.map +0 -1
  299. package/dist/chunk-JYQTXEIO.js.map +0 -1
  300. package/dist/chunk-LRAZDV5X.js.map +0 -1
  301. package/dist/chunk-MRIBLZL3.js +0 -86
  302. package/dist/chunk-MRIBLZL3.js.map +0 -1
  303. package/dist/chunk-MUWOSVEP.js.map +0 -1
  304. package/dist/chunk-OVZDFEOR.js.map +0 -1
  305. package/dist/chunk-PEULZC6M.js.map +0 -1
  306. package/dist/chunk-PFSNOPBQ.js.map +0 -1
  307. package/dist/chunk-PLI5TV7N.js.map +0 -1
  308. package/dist/chunk-Q6W2CMEJ.js.map +0 -1
  309. package/dist/chunk-RTZVQAJ7.js.map +0 -1
  310. package/dist/chunk-TBKOGSYR.js.map +0 -1
  311. package/dist/chunk-UMLVJTYV.js.map +0 -1
  312. package/dist/chunk-UND4XIB6.js.map +0 -1
  313. package/dist/chunk-VCGTOS2A.js.map +0 -1
  314. package/dist/chunk-VE6YVP32.js +0 -19
  315. package/dist/chunk-VE6YVP32.js.map +0 -1
  316. package/dist/chunk-VPSUZLOJ.js.map +0 -1
  317. package/dist/chunk-VRBCTEKQ.js.map +0 -1
  318. package/dist/chunk-W3XXT26A.js.map +0 -1
  319. package/dist/chunk-XG3PTSCD.js.map +0 -1
  320. package/dist/chunk-Y2RKOPNC.js.map +0 -1
  321. package/dist/chunk-YMYK7US4.js.map +0 -1
  322. package/dist/chunk-YS3POABP.js.map +0 -1
  323. package/dist/chunk-YTXSFG3C.js.map +0 -1
  324. package/dist/executor-AS2IDHKZ.js +0 -11
  325. package/dist/executor-HLXFXNFM.js +0 -8
  326. package/dist/executor-HN6YBHZ5.js +0 -8
  327. package/dist/fanout-sidecar-VJ52RIEY.js.map +0 -1
  328. package/dist/issue-ORP37MVW.js +0 -12
  329. package/dist/mime-magic-CBBSOkjm.d.cts +0 -50
  330. package/dist/mime-magic-CBBSOkjm.d.ts +0 -50
  331. package/dist/noydb-5H3C24GG.js +0 -34
  332. package/dist/read-only-facade-ITU6L7BL.js +0 -7
  333. package/dist/registry-7HE6VJGC.js +0 -8
  334. package/dist/registry-PSIPG2QR.js +0 -8
  335. package/dist/registry-RFGGMVNJ.js +0 -7
  336. package/dist/with-derivation-BKXXa8Vt.d.ts +0 -13
  337. package/dist/with-derivation-BjQ7q4NE.d.cts +0 -13
  338. package/dist/with-guard-C25yNjzd.d.ts +0 -18
  339. package/dist/with-guard-DQme5DKE.d.cts +0 -18
  340. /package/dist/{chunk-7Q5PLD5C.js.map → chunk-7MRT7EPB.js.map} +0 -0
  341. /package/dist/{chunk-G6FRSBKK.js.map → chunk-AI4USDRI.js.map} +0 -0
  342. /package/dist/{chunk-NWZ3I6R6.js.map → chunk-EYK72OTL.js.map} +0 -0
  343. /package/dist/{chunk-7BUTTVMR.js.map → chunk-F5GWNSE2.js.map} +0 -0
  344. /package/dist/{chunk-AHPFONIL.js.map → chunk-F5ILTHMU.js.map} +0 -0
  345. /package/dist/{chunk-QPEXPHJR.js.map → chunk-I3IYTUUI.js.map} +0 -0
  346. /package/dist/{chunk-3QAKZ37R.js.map → chunk-IVZWHIEK.js.map} +0 -0
  347. /package/dist/{chunk-243PNUA6.js.map → chunk-JOK73NDT.js.map} +0 -0
  348. /package/dist/{chunk-VK5EER6C.js.map → chunk-SQKAECUL.js.map} +0 -0
  349. /package/dist/{chunk-4HIL6AHQ.js.map → chunk-TAMRU7A2.js.map} +0 -0
  350. /package/dist/{chunk-QXQRKXCU.js.map → chunk-TGIJTNM3.js.map} +0 -0
  351. /package/dist/{chunk-3Y53S2SA.js.map → chunk-UU6M64HI.js.map} +0 -0
  352. /package/dist/{crypto-5ZDIY3NG.js.map → crypto-456N7UVX.js.map} +0 -0
  353. /package/dist/{delegation-QYXZW25W.js.map → delegation-DP4COTXB.js.map} +0 -0
  354. /package/dist/{executor-AS2IDHKZ.js.map → executor-4IEW4KG5.js.map} +0 -0
  355. /package/dist/{executor-HLXFXNFM.js.map → executor-KYJCJCIN.js.map} +0 -0
  356. /package/dist/{executor-HN6YBHZ5.js.map → executor-W7VIBOBZ.js.map} +0 -0
  357. /package/dist/{issue-ORP37MVW.js.map → forget/index.js.map} +0 -0
  358. /package/dist/{ledger-3IU5GMXA.js.map → issue-JXC6T2QR.js.map} +0 -0
  359. /package/dist/{noydb-5H3C24GG.js.map → ledger-I7JUYP4L.js.map} +0 -0
  360. /package/dist/{public-envelope-U3CMEOMV.js.map → noydb-ZZCRF6TE.js.map} +0 -0
  361. /package/dist/{read-only-facade-ITU6L7BL.js.map → public-envelope-5XRTUNKF.js.map} +0 -0
  362. /package/dist/{registry-3ALP62P6.js.map → read-only-facade-EX6WZZBP.js.map} +0 -0
  363. /package/dist/{registry-7HE6VJGC.js.map → registry-ATRHOG5B.js.map} +0 -0
  364. /package/dist/{registry-PSIPG2QR.js.map → registry-DKEXOJVO.js.map} +0 -0
  365. /package/dist/{registry-RFGGMVNJ.js.map → registry-LEHB26TY.js.map} +0 -0
  366. /package/dist/{revoke-KY2GB4KP.js.map → registry-NWHOLD5M.js.map} +0 -0
  367. /package/dist/{signer-GRI5TZKH.js.map → revoke-5IEK22KT.js.map} +0 -0
  368. /package/dist/{stale-OTOF3FH7.js.map → signer-I6YARZQA.js.map} +0 -0
@@ -22,6 +22,9 @@ var query_exports = {};
22
22
  __export(query_exports, {
23
23
  Aggregation: () => Aggregation,
24
24
  CollectionIndexes: () => CollectionIndexes,
25
+ CrossJoinSourceUnknownError: () => CrossJoinSourceUnknownError,
26
+ CrossJoinTooLargeError: () => CrossJoinTooLargeError,
27
+ DEFAULT_CROSS_JOIN_MAX_ROWS: () => DEFAULT_CROSS_JOIN_MAX_ROWS,
25
28
  DEFAULT_JOIN_MAX_ROWS: () => DEFAULT_JOIN_MAX_ROWS,
26
29
  DanglingReferenceError: () => DanglingReferenceError,
27
30
  GROUPBY_MAX_CARDINALITY: () => GROUPBY_MAX_CARDINALITY,
@@ -54,84 +57,156 @@ __export(query_exports, {
54
57
  });
55
58
  module.exports = __toCommonJS(query_exports);
56
59
 
57
- // src/query/predicate.ts
58
- function readPath(record, path) {
59
- if (record === null || record === void 0) return void 0;
60
- if (!path.includes(".")) {
61
- return record[path];
60
+ // src/money/fixed-point.ts
61
+ function expandExponent(s) {
62
+ const m = /^([+-]?)(\d+)(?:\.(\d+))?[eE]([+-]?\d+)$/.exec(s);
63
+ if (!m) return s;
64
+ const sign = m[1] === "-" ? "-" : "";
65
+ const intp = m[2];
66
+ const frac = m[3] ?? "";
67
+ const exp = Number(m[4]);
68
+ const digits = intp + frac;
69
+ const pointPos = intp.length + exp;
70
+ let body;
71
+ if (pointPos <= 0) {
72
+ body = "0." + "0".repeat(-pointPos) + digits;
73
+ } else if (pointPos >= digits.length) {
74
+ body = digits + "0".repeat(pointPos - digits.length);
75
+ } else {
76
+ body = digits.slice(0, pointPos) + "." + digits.slice(pointPos);
62
77
  }
63
- const segments = path.split(".");
64
- let cursor = record;
65
- for (const segment of segments) {
66
- if (cursor === null || cursor === void 0) return void 0;
67
- cursor = cursor[segment];
78
+ return sign + body;
79
+ }
80
+ function toCanonicalDecimalString(input) {
81
+ let s;
82
+ if (typeof input === "number") {
83
+ if (!Number.isFinite(input)) return null;
84
+ s = String(input);
85
+ } else {
86
+ s = input.trim();
68
87
  }
69
- return cursor;
88
+ s = expandExponent(s);
89
+ if (s.startsWith("+")) s = s.slice(1);
90
+ if (!/^-?(\d+(\.\d*)?|\.\d+)$/.test(s)) return null;
91
+ return s;
70
92
  }
71
- function evaluateFieldClause(record, clause) {
72
- const actual = readPath(record, clause.field);
73
- const { op, value } = clause;
74
- switch (op) {
75
- case "==":
76
- return actual === value;
77
- case "!=":
78
- return actual !== value;
79
- case "<":
80
- return isComparable(actual, value) && actual < value;
81
- case "<=":
82
- return isComparable(actual, value) && actual <= value;
83
- case ">":
84
- return isComparable(actual, value) && actual > value;
85
- case ">=":
86
- return isComparable(actual, value) && actual >= value;
87
- case "in":
88
- return Array.isArray(value) && value.includes(actual);
89
- case "contains":
90
- if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
91
- if (Array.isArray(actual)) return actual.includes(value);
93
+ function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
94
+ switch (mode) {
95
+ case "up":
96
+ return true;
97
+ case "down":
92
98
  return false;
93
- case "startsWith":
94
- return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
95
- case "between": {
96
- if (!Array.isArray(value) || value.length !== 2) return false;
97
- const [lo, hi] = value;
98
- if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
99
- return actual >= lo && actual <= hi;
100
- }
101
- default: {
102
- const _exhaustive = op;
103
- void _exhaustive;
104
- return false;
105
- }
99
+ case "ceil":
100
+ return !negative;
101
+ case "floor":
102
+ return negative;
103
+ case "half-up":
104
+ return firstDiscarded >= 5;
105
+ case "half-down":
106
+ return firstDiscarded > 5 || firstDiscarded === 5 && hasMoreNonZeroAfterFirst;
107
+ case "half-even":
108
+ if (firstDiscarded > 5) return true;
109
+ if (firstDiscarded < 5) return false;
110
+ return hasMoreNonZeroAfterFirst || lastKeptDigit % 2 === 1;
106
111
  }
107
112
  }
108
- function isComparable(a, b) {
109
- if (typeof a === "number" && typeof b === "number") return true;
110
- if (typeof a === "string" && typeof b === "string") return true;
111
- if (a instanceof Date && b instanceof Date) return true;
112
- return false;
113
+ function parseToScaledInt(input, scale, rounding) {
114
+ const canonical = toCanonicalDecimalString(input);
115
+ if (canonical === null) return { ok: false, reason: "nonfinite" };
116
+ const negative = canonical.startsWith("-");
117
+ const unsigned = negative ? canonical.slice(1) : canonical;
118
+ const dot = unsigned.indexOf(".");
119
+ const intPart = dot === -1 ? unsigned : unsigned.slice(0, dot);
120
+ const fracPart = dot === -1 ? "" : unsigned.slice(dot + 1);
121
+ const intDigits = intPart === "" ? "0" : intPart;
122
+ if (fracPart.length <= scale) {
123
+ const keep2 = fracPart.padEnd(scale, "0");
124
+ const magnitude2 = BigInt(intDigits + keep2);
125
+ return { ok: true, value: negative && magnitude2 !== 0n ? -magnitude2 : magnitude2 };
126
+ }
127
+ const keep = fracPart.slice(0, scale);
128
+ const tail = fracPart.slice(scale);
129
+ const magnitudeDigits = intDigits + keep;
130
+ let magnitude = BigInt(magnitudeDigits);
131
+ if (/^0+$/.test(tail)) {
132
+ return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
133
+ }
134
+ if (rounding === void 0) return { ok: false, reason: "precision" };
135
+ const lastKeptDigit = Number(magnitudeDigits[magnitudeDigits.length - 1]);
136
+ const firstDiscarded = Number(tail[0]);
137
+ const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
138
+ if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
139
+ magnitude += 1n;
140
+ }
141
+ return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
113
142
  }
114
- function evaluateClause(record, clause) {
115
- switch (clause.type) {
116
- case "field":
117
- return evaluateFieldClause(record, clause);
118
- case "filter":
119
- return clause.fn(record);
120
- case "wherePredicate":
121
- return clause.fn(record, clause.ctx);
122
- case "group":
123
- if (clause.op === "and") {
124
- for (const child of clause.clauses) {
125
- if (!evaluateClause(record, child)) return false;
126
- }
127
- return true;
128
- } else {
129
- for (const child of clause.clauses) {
130
- if (evaluateClause(record, child)) return true;
131
- }
132
- return false;
133
- }
134
- }
143
+ function formatScaledInt(value, scale) {
144
+ const negative = value < 0n;
145
+ const abs = (negative ? -value : value).toString();
146
+ if (scale === 0) return (negative ? "-" : "") + abs;
147
+ const padded = abs.padStart(scale + 1, "0");
148
+ const cut = padded.length - scale;
149
+ const intPart = padded.slice(0, cut);
150
+ const fracPart = padded.slice(cut);
151
+ return (negative ? "-" : "") + intPart + "." + fracPart;
152
+ }
153
+
154
+ // src/money/iso4217.ts
155
+ var MINOR_UNITS = {
156
+ // 2-decimal majors
157
+ EUR: 2,
158
+ USD: 2,
159
+ GBP: 2,
160
+ CHF: 2,
161
+ CAD: 2,
162
+ AUD: 2,
163
+ NZD: 2,
164
+ SGD: 2,
165
+ HKD: 2,
166
+ CNY: 2,
167
+ INR: 2,
168
+ BRL: 2,
169
+ MXN: 2,
170
+ ZAR: 2,
171
+ RUB: 2,
172
+ TRY: 2,
173
+ PLN: 2,
174
+ SEK: 2,
175
+ NOK: 2,
176
+ DKK: 2,
177
+ CZK: 2,
178
+ HUF: 2,
179
+ RON: 2,
180
+ ILS: 2,
181
+ THB: 2,
182
+ PHP: 2,
183
+ MYR: 2,
184
+ IDR: 2,
185
+ AED: 2,
186
+ SAR: 2,
187
+ QAR: 2,
188
+ EGP: 2,
189
+ // 0-decimal
190
+ JPY: 0,
191
+ KRW: 0,
192
+ ISK: 0,
193
+ CLP: 0,
194
+ VND: 0,
195
+ XOF: 0,
196
+ XAF: 0,
197
+ PYG: 0,
198
+ // 3-decimal
199
+ BHD: 3,
200
+ KWD: 3,
201
+ OMR: 3,
202
+ TND: 3,
203
+ JOD: 3,
204
+ IQD: 3,
205
+ LYD: 3
206
+ };
207
+ function scaleForCurrency(code) {
208
+ const v = MINOR_UNITS[code];
209
+ return v === void 0 ? null : v;
135
210
  }
136
211
 
137
212
  // src/errors.ts
@@ -144,6 +219,12 @@ var NoydbError = class extends Error {
144
219
  this.code = code;
145
220
  }
146
221
  };
222
+ var ValidationError = class extends NoydbError {
223
+ constructor(message = "Validation error") {
224
+ super("VALIDATION_ERROR", message);
225
+ this.name = "ValidationError";
226
+ }
227
+ };
147
228
  var GroupCardinalityError = class extends NoydbError {
148
229
  /** The field being grouped on. */
149
230
  field;
@@ -194,6 +275,18 @@ var IndexWriteFailureError = class extends NoydbError {
194
275
  this.cause = args.cause;
195
276
  }
196
277
  };
278
+ var LocaleNotSpecifiedError = class extends NoydbError {
279
+ /** The field name that required a locale. */
280
+ field;
281
+ constructor(field, message) {
282
+ super(
283
+ "LOCALE_NOT_SPECIFIED",
284
+ message ?? `Cannot read i18nText field "${field}" without a locale. Pass { locale } to get()/list()/query() or set a default via openVault(name, { locale }).`
285
+ );
286
+ this.name = "LocaleNotSpecifiedError";
287
+ this.field = field;
288
+ }
289
+ };
197
290
  var JoinTooLargeError = class extends NoydbError {
198
291
  leftRows;
199
292
  rightRows;
@@ -208,6 +301,34 @@ var JoinTooLargeError = class extends NoydbError {
208
301
  this.side = opts.side;
209
302
  }
210
303
  };
304
+ var CrossJoinTooLargeError = class extends NoydbError {
305
+ target;
306
+ expected;
307
+ limit;
308
+ constructor(opts) {
309
+ super(
310
+ "CROSS_JOIN_TOO_LARGE",
311
+ `crossJoin("${opts.target}"): would produce ${opts.expected} rows, exceeding the limit of ${opts.limit}. Narrow the left side with .where() first, or raise the ceiling with crossJoin("${opts.target}", { ..., maxRows: ${opts.expected} }).`
312
+ );
313
+ this.name = "CrossJoinTooLargeError";
314
+ this.target = opts.target;
315
+ this.expected = opts.expected;
316
+ this.limit = opts.limit;
317
+ }
318
+ };
319
+ var CrossJoinSourceUnknownError = class extends NoydbError {
320
+ target;
321
+ leftCollection;
322
+ constructor(target, leftCollection) {
323
+ super(
324
+ "CROSS_JOIN_SOURCE_UNKNOWN",
325
+ `crossJoin("${target}"): collection "${target}" is not known in the vault (cross-joining from "${leftCollection}"). Make sure "${target}" is open in the same vault before executing this query.`
326
+ );
327
+ this.name = "CrossJoinSourceUnknownError";
328
+ this.target = target;
329
+ this.leftCollection = leftCollection;
330
+ }
331
+ };
211
332
  var DanglingReferenceError = class extends NoydbError {
212
333
  field;
213
334
  target;
@@ -221,6 +342,436 @@ var DanglingReferenceError = class extends NoydbError {
221
342
  }
222
343
  };
223
344
 
345
+ // src/money/descriptor.ts
346
+ var MoneyUnsupportedError = class extends NoydbError {
347
+ constructor(field, message) {
348
+ super(
349
+ "MONEY_UNSUPPORTED",
350
+ message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
351
+ );
352
+ this.field = field;
353
+ this.name = "MoneyUnsupportedError";
354
+ }
355
+ field;
356
+ };
357
+
358
+ // src/money/where.ts
359
+ function isMoneyValueObject(v) {
360
+ return typeof v === "object" && v !== null && "currency" in v;
361
+ }
362
+ function parseOperand(field, raw, desc) {
363
+ let amount;
364
+ let currency;
365
+ if (desc.mode === "fixed") {
366
+ currency = desc.fixedCurrency;
367
+ amount = raw;
368
+ } else if (isMoneyValueObject(raw)) {
369
+ currency = String(raw.currency);
370
+ amount = raw.amount;
371
+ } else {
372
+ const sole = desc.soleCurrency();
373
+ if (sole === void 0) {
374
+ throw new MoneyUnsupportedError(
375
+ `where("${field}"): field is multi-currency \u2014 compare against { amount, currency }, not a bare amount`
376
+ );
377
+ }
378
+ currency = sole;
379
+ amount = raw;
380
+ }
381
+ if (typeof amount !== "number" && typeof amount !== "string") {
382
+ throw new MoneyUnsupportedError(
383
+ `where("${field}"): operand ${JSON.stringify(raw)} is not a money amount`
384
+ );
385
+ }
386
+ const r = parseToScaledInt(amount, desc.scaleFor(currency), desc.rounding);
387
+ if (!r.ok) {
388
+ throw new MoneyUnsupportedError(
389
+ `where("${field}"): operand ${JSON.stringify(amount)} is not a finite decimal`
390
+ );
391
+ }
392
+ return { scaled: r.value.toString(), currency };
393
+ }
394
+ function moneyFieldClause(field, op, value, desc) {
395
+ switch (op) {
396
+ case "==":
397
+ case "!=":
398
+ case "<":
399
+ case "<=":
400
+ case ">":
401
+ case ">=": {
402
+ const e = parseOperand(field, value, desc);
403
+ return withMoney(field, op, value, desc, [e]);
404
+ }
405
+ case "between": {
406
+ if (!Array.isArray(value) || value.length !== 2) {
407
+ throw new MoneyUnsupportedError(`where("${field}"): 'between' needs a [lo, hi] tuple`);
408
+ }
409
+ const lo = parseOperand(field, value[0], desc);
410
+ const hi = parseOperand(field, value[1], desc);
411
+ if (lo.currency !== hi.currency) {
412
+ throw new MoneyUnsupportedError(
413
+ `where("${field}"): 'between' bounds mix currencies (${lo.currency} vs ${hi.currency})`
414
+ );
415
+ }
416
+ return withMoney(field, op, value, desc, [lo, hi]);
417
+ }
418
+ case "in": {
419
+ if (!Array.isArray(value)) {
420
+ throw new MoneyUnsupportedError(`where("${field}"): 'in' needs an array of amounts`);
421
+ }
422
+ return withMoney(field, op, value, desc, value.map((v) => parseOperand(field, v, desc)));
423
+ }
424
+ default:
425
+ throw new MoneyUnsupportedError(
426
+ `where("${field}"): operator '${op}' is not supported on a money field`
427
+ );
428
+ }
429
+ }
430
+ function withMoney(field, op, originalValue, desc, entries) {
431
+ const money = { mode: desc.mode, entries };
432
+ const value = desc.mode !== "fixed" ? originalValue : entries.length === 1 && op !== "in" && op !== "between" ? entries[0].scaled : entries.map((e) => e.scaled);
433
+ return { type: "field", field, op, value, money };
434
+ }
435
+ function readStored(actual, operand) {
436
+ let amount;
437
+ let currency;
438
+ if (operand.mode === "fixed") {
439
+ if (typeof actual !== "string" && typeof actual !== "number") return null;
440
+ amount = actual;
441
+ currency = operand.entries[0]?.currency ?? "";
442
+ } else {
443
+ if (!isMoneyValueObject(actual)) return null;
444
+ if (typeof actual.currency !== "string") return null;
445
+ amount = actual.amount;
446
+ currency = actual.currency;
447
+ }
448
+ if (typeof amount !== "string" && typeof amount !== "number") return null;
449
+ try {
450
+ return { scaled: BigInt(amount).toString(), currency };
451
+ } catch {
452
+ return null;
453
+ }
454
+ }
455
+ function evaluateMoneyClause(actual, op, operand) {
456
+ const stored = readStored(actual, operand);
457
+ if (stored === null) return op === "!=";
458
+ const a = BigInt(stored.scaled);
459
+ if (op === "in") {
460
+ return operand.entries.some(
461
+ (e2) => e2.currency === stored.currency && BigInt(e2.scaled) === a
462
+ );
463
+ }
464
+ if (op === "between") {
465
+ const [lo, hi] = operand.entries;
466
+ if (!lo || !hi || lo.currency !== stored.currency) return false;
467
+ return a >= BigInt(lo.scaled) && a <= BigInt(hi.scaled);
468
+ }
469
+ const e = operand.entries[0];
470
+ if (!e) return false;
471
+ if (e.currency !== stored.currency) return op === "!=";
472
+ const b = BigInt(e.scaled);
473
+ switch (op) {
474
+ case "==":
475
+ return a === b;
476
+ case "!=":
477
+ return a !== b;
478
+ case "<":
479
+ return a < b;
480
+ case "<=":
481
+ return a <= b;
482
+ case ">":
483
+ return a > b;
484
+ case ">=":
485
+ return a >= b;
486
+ default:
487
+ return false;
488
+ }
489
+ }
490
+
491
+ // src/query/predicate.ts
492
+ function readPath(record, path) {
493
+ if (record === null || record === void 0) return void 0;
494
+ if (!path.includes(".")) {
495
+ return record[path];
496
+ }
497
+ const segments = path.split(".");
498
+ let cursor = record;
499
+ for (const segment of segments) {
500
+ if (cursor === null || cursor === void 0) return void 0;
501
+ cursor = cursor[segment];
502
+ }
503
+ return cursor;
504
+ }
505
+ function evaluateFieldClause(record, clause) {
506
+ const actual = readPath(record, clause.field);
507
+ const { op, value } = clause;
508
+ if (clause.money) return evaluateMoneyClause(actual, op, clause.money);
509
+ switch (op) {
510
+ case "==":
511
+ return actual === value;
512
+ case "!=":
513
+ return actual !== value;
514
+ case "<":
515
+ return isComparable(actual, value) && actual < value;
516
+ case "<=":
517
+ return isComparable(actual, value) && actual <= value;
518
+ case ">":
519
+ return isComparable(actual, value) && actual > value;
520
+ case ">=":
521
+ return isComparable(actual, value) && actual >= value;
522
+ case "in":
523
+ return Array.isArray(value) && value.includes(actual);
524
+ case "contains":
525
+ if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
526
+ if (Array.isArray(actual)) return actual.includes(value);
527
+ return false;
528
+ case "startsWith":
529
+ return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
530
+ case "between": {
531
+ if (!Array.isArray(value) || value.length !== 2) return false;
532
+ const [lo, hi] = value;
533
+ if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
534
+ return actual >= lo && actual <= hi;
535
+ }
536
+ default: {
537
+ const _exhaustive = op;
538
+ void _exhaustive;
539
+ return false;
540
+ }
541
+ }
542
+ }
543
+ function isComparable(a, b) {
544
+ if (typeof a === "number" && typeof b === "number") return true;
545
+ if (typeof a === "string" && typeof b === "string") return true;
546
+ if (a instanceof Date && b instanceof Date) return true;
547
+ return false;
548
+ }
549
+ function evaluateClause(record, clause, fnRecord) {
550
+ switch (clause.type) {
551
+ case "field":
552
+ return evaluateFieldClause(record, clause);
553
+ case "filter":
554
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record);
555
+ case "wherePredicate":
556
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record, clause.ctx);
557
+ case "crossJoin":
558
+ throw new Error(
559
+ `evaluateClause: 'crossJoin' clauses are expansion primitives and are not evaluated per-record. This is a query planner routing error \u2014 crossJoin clauses must be extracted from the clause list before calling evaluateClause or filterRecords.`
560
+ );
561
+ case "group":
562
+ if (clause.op === "and") {
563
+ for (const child of clause.clauses) {
564
+ if (!evaluateClause(record, child, fnRecord)) return false;
565
+ }
566
+ return true;
567
+ } else {
568
+ for (const child of clause.clauses) {
569
+ if (evaluateClause(record, child, fnRecord)) return true;
570
+ }
571
+ return false;
572
+ }
573
+ }
574
+ }
575
+ function hasFnClause(clauses) {
576
+ for (const c of clauses) {
577
+ if (c.type === "filter" || c.type === "wherePredicate") return true;
578
+ if (c.type === "group" && hasFnClause(c.clauses)) return true;
579
+ }
580
+ return false;
581
+ }
582
+
583
+ // src/i18n/policy.ts
584
+ function resolvePolicy(onMissing, layer) {
585
+ const explicit = onMissing && typeof onMissing === "object" ? onMissing[layer] : void 0;
586
+ const scalar = typeof onMissing === "string" ? onMissing : void 0;
587
+ const layerDefault = layer === "guard" ? "substitute" : void 0;
588
+ return explicit ?? layerDefault ?? scalar ?? "throw";
589
+ }
590
+
591
+ // src/i18n/script.ts
592
+ var LATIN_BASE = /* @__PURE__ */ new Set([
593
+ "en",
594
+ "fr",
595
+ "de",
596
+ "es",
597
+ "it",
598
+ "pt",
599
+ "nl",
600
+ "sv",
601
+ "no",
602
+ "da",
603
+ "fi",
604
+ "is",
605
+ "pl",
606
+ "cs",
607
+ "sk",
608
+ "hu",
609
+ "ro",
610
+ "hr",
611
+ "sl",
612
+ "et",
613
+ "lv",
614
+ "lt",
615
+ "tr",
616
+ "vi",
617
+ "id",
618
+ "ms",
619
+ "tl",
620
+ "sw",
621
+ "af",
622
+ "ca",
623
+ "gl",
624
+ "eu",
625
+ "cy",
626
+ "ga"
627
+ ]);
628
+ var SCRIPT_TABLE = {
629
+ th: ["Thai"],
630
+ ko: ["Hangul", "Han"],
631
+ ja: ["Han", "Hiragana", "Katakana"],
632
+ zh: ["Han"],
633
+ ar: ["Arabic"],
634
+ fa: ["Arabic"],
635
+ ur: ["Arabic"],
636
+ ru: ["Cyrillic"],
637
+ uk: ["Cyrillic"],
638
+ bg: ["Cyrillic"],
639
+ sr: ["Cyrillic"],
640
+ he: ["Hebrew"],
641
+ el: ["Greek"],
642
+ hi: ["Devanagari"],
643
+ ta: ["Tamil"],
644
+ km: ["Khmer"],
645
+ lo: ["Lao"],
646
+ my: ["Myanmar"]
647
+ };
648
+ var SUBTAG_SCRIPTS = {
649
+ Latn: ["Latin"],
650
+ Cyrl: ["Cyrillic", "Latin"],
651
+ Hans: ["Han", "Latin"],
652
+ Hant: ["Han", "Latin"],
653
+ Thai: ["Thai", "Latin"],
654
+ Arab: ["Arabic", "Latin"]
655
+ };
656
+ function inferScripts(locale) {
657
+ const parts = locale.split("-");
658
+ const subtag = parts.find((t) => /^[A-Z][a-z]{3}$/.test(t));
659
+ if (subtag && SUBTAG_SCRIPTS[subtag]) return SUBTAG_SCRIPTS[subtag];
660
+ const base = (parts[0] ?? "").toLowerCase();
661
+ if (LATIN_BASE.has(base)) return ["Latin"];
662
+ const primary = SCRIPT_TABLE[base];
663
+ if (primary) return [...primary, "Latin"];
664
+ return ["Latin"];
665
+ }
666
+ var BASELINE = String.raw`\p{White_Space}\p{Script=Common}\p{Script=Inherited}\p{Mark}`;
667
+
668
+ // src/i18n/core.ts
669
+ function toChain(fallback) {
670
+ return Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
671
+ }
672
+ function pickFromChain(value, chain) {
673
+ for (const fb of chain) {
674
+ if (fb === "any") {
675
+ const any = Object.values(value).find((v) => v !== "");
676
+ if (any !== void 0) return any;
677
+ } else if (value[fb] !== void 0 && value[fb] !== "") {
678
+ return value[fb];
679
+ }
680
+ }
681
+ return void 0;
682
+ }
683
+ function resolveI18nText(value, locale, fallback, field, opts) {
684
+ if (locale === "raw") {
685
+ return value;
686
+ }
687
+ if (!locale) {
688
+ throw new LocaleNotSpecifiedError(field ?? "<unknown>");
689
+ }
690
+ if (value[locale] !== void 0 && value[locale] !== "") {
691
+ return value[locale];
692
+ }
693
+ const policy = opts?.policy ?? "throw";
694
+ const callerChain = toChain(fallback);
695
+ const callerHit = pickFromChain(value, callerChain);
696
+ if (callerHit !== void 0) return callerHit;
697
+ if (policy === "substitute") {
698
+ const subHit = pickFromChain(value, toChain(opts?.substitute));
699
+ if (subHit !== void 0) return subHit;
700
+ if (opts?.smartSubstitute) {
701
+ const smartHit = pickNearestScript(value, locale);
702
+ if (smartHit !== void 0) return smartHit;
703
+ }
704
+ }
705
+ if (policy === "throw") {
706
+ throw new LocaleNotSpecifiedError(
707
+ field ?? "<unknown>",
708
+ `No translation available for locale "${locale}"` + (callerChain.length > 0 ? ` or fallback chain [${callerChain.join(", ")}]` : "") + "."
709
+ );
710
+ }
711
+ return null;
712
+ }
713
+ function pickNearestScript(value, target) {
714
+ const targetScript = inferScripts(target)[0] ?? "Latin";
715
+ let best;
716
+ for (const [loc, v] of Object.entries(value)) {
717
+ if (typeof v !== "string" || v === "") continue;
718
+ const s = inferScripts(loc)[0] ?? "Latin";
719
+ const score = s === targetScript ? 0 : s === "Latin" ? 1 : 2;
720
+ if (best === void 0 || score < best.score) best = { score, v };
721
+ if (score === 0) break;
722
+ }
723
+ return best?.v;
724
+ }
725
+ function applyAtPath(obj, path, locale, fallback, opts) {
726
+ const arrayIdx = path.indexOf("[].");
727
+ if (arrayIdx !== -1) {
728
+ const arrayKey = path.slice(0, arrayIdx);
729
+ const restPath = path.slice(arrayIdx + 3);
730
+ const arr = obj[arrayKey];
731
+ if (!Array.isArray(arr)) return obj;
732
+ return {
733
+ ...obj,
734
+ [arrayKey]: arr.map((item) => {
735
+ if (!item || typeof item !== "object" || Array.isArray(item)) return item;
736
+ return applyAtPath(item, restPath, locale, fallback, opts);
737
+ })
738
+ };
739
+ }
740
+ const dotIdx = path.indexOf(".");
741
+ if (dotIdx !== -1) {
742
+ const head = path.slice(0, dotIdx);
743
+ const rest = path.slice(dotIdx + 1);
744
+ const nested = obj[head];
745
+ if (!nested || typeof nested !== "object" || Array.isArray(nested)) return obj;
746
+ return {
747
+ ...obj,
748
+ [head]: applyAtPath(nested, rest, locale, fallback, opts)
749
+ };
750
+ }
751
+ const raw = obj[path];
752
+ if (raw === void 0 || raw === null) return obj;
753
+ if (typeof raw !== "object" || Array.isArray(raw)) return obj;
754
+ return {
755
+ ...obj,
756
+ [path]: resolveI18nText(raw, locale, fallback, path, opts)
757
+ };
758
+ }
759
+ function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
760
+ const fieldNames = Object.keys(i18nFields);
761
+ if (fieldNames.length === 0) return record;
762
+ let result = record;
763
+ for (const [field, descriptor] of Object.entries(i18nFields)) {
764
+ const { onMissing, substitute, smartSubstitute } = descriptor.options;
765
+ const opts = {
766
+ policy: resolvePolicy(onMissing, layer),
767
+ ...substitute !== void 0 ? { substitute } : {},
768
+ ...smartSubstitute ? { smartSubstitute } : {}
769
+ };
770
+ result = applyAtPath(result, field, locale, fallback, opts);
771
+ }
772
+ return result;
773
+ }
774
+
224
775
  // src/query/join.ts
225
776
  var DEFAULT_JOIN_MAX_ROWS = 5e4;
226
777
  var JOIN_WARN_FRACTION = 0.8;
@@ -249,15 +800,15 @@ function warnCeilingApproaching(target, side, rows, maxRows) {
249
800
  `[noy-db] .join() ${side} side is at ${pct}% of the ${maxRows}-row ceiling for target "${target}" (${rows} rows). Streaming joins over scan() are not yet supported for collections that need to exceed this.`
250
801
  );
251
802
  }
252
- function applyJoins(rows, joins, context) {
803
+ function applyJoins(rows, joins, context, locale) {
253
804
  if (joins.length === 0) return [...rows];
254
805
  let result = [...rows];
255
806
  for (const leg of joins) {
256
- result = applyOneJoin(result, leg, context);
807
+ result = applyOneJoin(result, leg, context, locale);
257
808
  }
258
809
  return result;
259
810
  }
260
- function applyOneJoin(leftRows, leg, context) {
811
+ function applyOneJoin(leftRows, leg, context, locale) {
261
812
  if (leg.isDictJoin) {
262
813
  const dictSource = context.resolveDictSource?.(leg.field);
263
814
  if (!dictSource) {
@@ -312,24 +863,27 @@ function applyOneJoin(leftRows, leg, context) {
312
863
  if (rightSnapshot.length > maxRows * JOIN_WARN_FRACTION) {
313
864
  warnCeilingApproaching(leg.target, "right", rightSnapshot.length, maxRows);
314
865
  }
866
+ const effLocale = locale ?? context.defaultLocale;
867
+ const i18nResolve = effLocale !== void 0 && source.i18nFields !== void 0 ? (right) => right !== null && typeof right === "object" ? applyI18nLocale(right, source.i18nFields, effLocale, void 0, "join") : right : void 0;
315
868
  const strategy = leg.strategy ?? (source.lookupById ? "nested" : "hash");
316
869
  if (strategy === "nested" && source.lookupById) {
317
870
  const lookup = (id) => source.lookupById?.(id);
318
- return nestedLoopJoin(leftRows, leg, lookup);
871
+ return nestedLoopJoin(leftRows, leg, lookup, i18nResolve);
319
872
  }
320
- return hashJoin(leftRows, leg, rightSnapshot);
873
+ return hashJoin(leftRows, leg, rightSnapshot, i18nResolve);
321
874
  }
322
- function nestedLoopJoin(leftRows, leg, lookupById) {
875
+ function nestedLoopJoin(leftRows, leg, lookupById, i18nResolve) {
323
876
  const out = [];
324
877
  for (const left of leftRows) {
325
878
  const rawId = readPath(left, leg.field);
326
879
  const key = coerceRefKey(rawId);
327
- const right = key === null ? void 0 : lookupById(key);
880
+ let right = key === null ? void 0 : lookupById(key);
881
+ if (i18nResolve && right !== void 0) right = i18nResolve(right);
328
882
  out.push(attachJoin(left, leg, right, rawId));
329
883
  }
330
884
  return out;
331
885
  }
332
- function hashJoin(leftRows, leg, rightSnapshot) {
886
+ function hashJoin(leftRows, leg, rightSnapshot, i18nResolve) {
333
887
  const rightMap = /* @__PURE__ */ new Map();
334
888
  for (const record of rightSnapshot) {
335
889
  const rawId = readPath(record, "id");
@@ -342,7 +896,8 @@ function hashJoin(leftRows, leg, rightSnapshot) {
342
896
  for (const left of leftRows) {
343
897
  const rawId = readPath(left, leg.field);
344
898
  const key = coerceRefKey(rawId);
345
- const right = key === null ? void 0 : rightMap.get(key);
899
+ let right = key === null ? void 0 : rightMap.get(key);
900
+ if (i18nResolve && right !== void 0) right = i18nResolve(right);
346
901
  out.push(attachJoin(left, leg, right, rawId));
347
902
  }
348
903
  return out;
@@ -466,6 +1021,386 @@ var NO_AGGREGATE = {
466
1021
  }
467
1022
  };
468
1023
 
1024
+ // src/money/money-reducer.ts
1025
+ function toScaledIntFromAny(v, scale) {
1026
+ if (typeof v === "bigint") return v;
1027
+ if (typeof v === "number") {
1028
+ const r = parseToScaledInt(v, scale);
1029
+ return r.ok ? r.value : null;
1030
+ }
1031
+ if (typeof v === "string") {
1032
+ if (!v.includes(".")) {
1033
+ try {
1034
+ return BigInt(v);
1035
+ } catch {
1036
+ return null;
1037
+ }
1038
+ }
1039
+ const r = parseToScaledInt(v, scale);
1040
+ return r.ok ? r.value : null;
1041
+ }
1042
+ return null;
1043
+ }
1044
+ function readMoney(record, field, desc) {
1045
+ const raw = readPath(record, field);
1046
+ if (raw === null || raw === void 0) return null;
1047
+ if (desc.mode === "fixed") {
1048
+ const cur = desc.fixedCurrency;
1049
+ const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
1050
+ return value2 === null ? null : { currency: cur, value: value2 };
1051
+ }
1052
+ if (typeof raw !== "object") return null;
1053
+ const o = raw;
1054
+ if (typeof o.currency !== "string") return null;
1055
+ const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
1056
+ const value = toScaledIntFromAny(o.amount, scale);
1057
+ return value === null ? null : { currency: o.currency, value };
1058
+ }
1059
+ function targetScaleFor(desc, currency) {
1060
+ if (desc.allows(currency)) return desc.scaleFor(currency);
1061
+ const s = scaleForCurrency(currency);
1062
+ if (s === null) {
1063
+ throw new Error(`money: cannot determine scale for conversion target "${currency}"`);
1064
+ }
1065
+ return s;
1066
+ }
1067
+ function parseRate(rate) {
1068
+ const s = String(rate).trim();
1069
+ const neg = s.startsWith("-");
1070
+ const body = neg ? s.slice(1) : s;
1071
+ const dot = body.indexOf(".");
1072
+ const intPart = dot === -1 ? body : body.slice(0, dot);
1073
+ const fracPart = dot === -1 ? "" : body.slice(dot + 1);
1074
+ const int = BigInt((intPart === "" ? "0" : intPart) + fracPart);
1075
+ return { int: neg ? -int : int, scale: fracPart.length };
1076
+ }
1077
+ function divRoundHalfEven(n, d) {
1078
+ const q = n / d;
1079
+ const r = n % d;
1080
+ const twiceR = (r < 0n ? -r : r) * 2n;
1081
+ if (twiceR < d) return q;
1082
+ if (twiceR > d) return q + (n < 0n ? -1n : 1n);
1083
+ return q % 2n === 0n ? q : q + (n < 0n ? -1n : 1n);
1084
+ }
1085
+ function convertScaled(value, srcScale, rate, targetScale) {
1086
+ const { int: rateInt, scale: rateScale } = parseRate(rate);
1087
+ const product = value * rateInt;
1088
+ const curScale = srcScale + rateScale;
1089
+ if (curScale === targetScale) return product;
1090
+ if (curScale < targetScale) return product * 10n ** BigInt(targetScale - curScale);
1091
+ return divRoundHalfEven(product, 10n ** BigInt(curScale - targetScale));
1092
+ }
1093
+ function finalizeSum(state, desc, convertTo, fx) {
1094
+ if (convertTo !== void 0) {
1095
+ if (fx === void 0) {
1096
+ throw new Error(`money: sum convertTo "${convertTo}" requires an fx rate map`);
1097
+ }
1098
+ const targetScale = targetScaleFor(desc, convertTo);
1099
+ let total = 0n;
1100
+ for (const [cur, v] of state) {
1101
+ if (cur === convertTo) {
1102
+ total += convertScaled(v, desc.scaleFor(cur), 1, targetScale);
1103
+ continue;
1104
+ }
1105
+ const rate = fx[`${cur}->${convertTo}`];
1106
+ if (rate === void 0) {
1107
+ throw new Error(`money: no fx rate for "${cur}->${convertTo}"`);
1108
+ }
1109
+ total += convertScaled(v, desc.scaleFor(cur), rate, targetScale);
1110
+ }
1111
+ return formatScaledInt(total, targetScale);
1112
+ }
1113
+ if (desc.mode === "fixed") {
1114
+ const cur = desc.fixedCurrency;
1115
+ return formatScaledInt(state.get(cur) ?? 0n, desc.scaleFor(cur));
1116
+ }
1117
+ const out = {};
1118
+ for (const [cur, v] of state) out[cur] = formatScaledInt(v, desc.scaleFor(cur));
1119
+ return out;
1120
+ }
1121
+ function moneySumReducer(field, desc, convertTo, fx) {
1122
+ return {
1123
+ op: "sum",
1124
+ field,
1125
+ init: () => /* @__PURE__ */ new Map(),
1126
+ step: (state, record) => {
1127
+ const m = readMoney(record, field, desc);
1128
+ if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) + m.value);
1129
+ return state;
1130
+ },
1131
+ remove: (state, record) => {
1132
+ const m = readMoney(record, field, desc);
1133
+ if (m) state.set(m.currency, (state.get(m.currency) ?? 0n) - m.value);
1134
+ return state;
1135
+ },
1136
+ finalize: (state) => finalizeSum(state, desc, convertTo, fx)
1137
+ };
1138
+ }
1139
+ function extremum(values, op) {
1140
+ let out = values[0];
1141
+ for (let i = 1; i < values.length; i++) {
1142
+ const v = values[i];
1143
+ if (op === "min" ? v < out : v > out) out = v;
1144
+ }
1145
+ return out;
1146
+ }
1147
+ function moneyMinMaxReducer(op, field, desc) {
1148
+ return {
1149
+ op,
1150
+ field,
1151
+ init: () => /* @__PURE__ */ new Map(),
1152
+ step: (state, record) => {
1153
+ const m = readMoney(record, field, desc);
1154
+ if (m) {
1155
+ const arr = state.get(m.currency);
1156
+ if (arr) arr.push(m.value);
1157
+ else state.set(m.currency, [m.value]);
1158
+ }
1159
+ return state;
1160
+ },
1161
+ remove: (state, record) => {
1162
+ const m = readMoney(record, field, desc);
1163
+ if (m) {
1164
+ const arr = state.get(m.currency);
1165
+ if (arr) {
1166
+ const idx = arr.indexOf(m.value);
1167
+ if (idx >= 0) arr.splice(idx, 1);
1168
+ }
1169
+ }
1170
+ return state;
1171
+ },
1172
+ finalize: (state) => {
1173
+ if (desc.mode === "fixed") {
1174
+ const cur = desc.fixedCurrency;
1175
+ const arr = state.get(cur);
1176
+ if (!arr || arr.length === 0) return null;
1177
+ return formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
1178
+ }
1179
+ const out = {};
1180
+ for (const [cur, arr] of state) {
1181
+ if (arr.length > 0) out[cur] = formatScaledInt(extremum(arr, op), desc.scaleFor(cur));
1182
+ }
1183
+ return out;
1184
+ }
1185
+ };
1186
+ }
1187
+ function wrapMoneyReducers(spec, moneyFields) {
1188
+ let changed = false;
1189
+ const out = {};
1190
+ for (const [key, reducer] of Object.entries(spec)) {
1191
+ const field = reducer.field;
1192
+ const desc = field ? moneyFields[field] : void 0;
1193
+ if (desc && reducer.op === "avg") {
1194
+ throw new MoneyUnsupportedError(
1195
+ field,
1196
+ `avg() is not supported on money field "${field}" in v1 \u2014 use sum() and count() and divide at the boundary.`
1197
+ );
1198
+ }
1199
+ if (desc && (reducer.op === "sum" || reducer.op === "min" || reducer.op === "max")) {
1200
+ changed = true;
1201
+ out[key] = reducer.op === "sum" ? moneySumReducer(field, desc, reducer.convertTo, reducer.fx) : moneyMinMaxReducer(reducer.op, field, desc);
1202
+ } else {
1203
+ out[key] = reducer;
1204
+ }
1205
+ }
1206
+ return changed ? out : spec;
1207
+ }
1208
+
1209
+ // src/money/paths.ts
1210
+ var SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
1211
+ var parseCache = /* @__PURE__ */ new Map();
1212
+ function parseMoneyPath(path) {
1213
+ const cached = parseCache.get(path);
1214
+ if (cached) return cached;
1215
+ if (typeof path !== "string" || path.length === 0) {
1216
+ throw new ValidationError("moneyFields: path must be a non-empty string");
1217
+ }
1218
+ const segments = [];
1219
+ for (const part of path.split(".")) {
1220
+ const m = SEGMENT_RE.exec(part);
1221
+ if (!m) {
1222
+ throw new ValidationError(
1223
+ `moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
1224
+ );
1225
+ }
1226
+ const array = m[2] === "[]";
1227
+ segments.push(
1228
+ m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
1229
+ );
1230
+ }
1231
+ parseCache.set(path, segments);
1232
+ return segments;
1233
+ }
1234
+ function isSimpleMoneyPath(path) {
1235
+ return !path.includes(".") && !path.includes("[") && !path.includes("*");
1236
+ }
1237
+ function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
1238
+ if (node === null || node === void 0) return node;
1239
+ const seg = segments[index];
1240
+ const last = index === segments.length - 1;
1241
+ if (seg.kind === "key") {
1242
+ if (typeof node !== "object" || Array.isArray(node)) {
1243
+ if (lenient) return node;
1244
+ throw new ValidationError(
1245
+ `moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
1246
+ );
1247
+ }
1248
+ const obj2 = node;
1249
+ if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
1250
+ if (seg.array) {
1251
+ const arr = obj2[seg.key];
1252
+ if (!Array.isArray(arr)) {
1253
+ if (lenient) return node;
1254
+ throw new ValidationError(
1255
+ `moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
1256
+ );
1257
+ }
1258
+ const cloned = [...arr];
1259
+ if (last) {
1260
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
1261
+ } else {
1262
+ for (let i = 0; i < cloned.length; i++) {
1263
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
1264
+ }
1265
+ }
1266
+ return { ...obj2, [seg.key]: cloned };
1267
+ }
1268
+ const clone2 = { ...obj2 };
1269
+ if (last) {
1270
+ visit(clone2, seg.key);
1271
+ } else {
1272
+ clone2[seg.key] = transformAtMoneyPath(clone2[seg.key], path, segments, index + 1, visit, lenient);
1273
+ }
1274
+ return clone2;
1275
+ }
1276
+ if (seg.array) {
1277
+ if (!Array.isArray(node)) {
1278
+ if (lenient) return node;
1279
+ throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
1280
+ }
1281
+ const cloned = [...node];
1282
+ if (last) {
1283
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
1284
+ } else {
1285
+ for (let i = 0; i < cloned.length; i++) {
1286
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
1287
+ }
1288
+ }
1289
+ return cloned;
1290
+ }
1291
+ if (typeof node !== "object" || Array.isArray(node)) {
1292
+ if (lenient) return node;
1293
+ throw new ValidationError(
1294
+ `moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
1295
+ );
1296
+ }
1297
+ const obj = node;
1298
+ const clone = { ...obj };
1299
+ for (const key of Object.keys(obj)) {
1300
+ const v = clone[key];
1301
+ if (v === null || v === void 0) continue;
1302
+ if (last) visit(clone, key);
1303
+ else clone[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
1304
+ }
1305
+ return clone;
1306
+ }
1307
+
1308
+ // src/money/normalize.ts
1309
+ function isMoneyValueObject2(v) {
1310
+ return typeof v === "object" && v !== null && "currency" in v;
1311
+ }
1312
+ function formatCurrency(decimal, currency, scale, locale) {
1313
+ const fmt = new Intl.NumberFormat(locale, {
1314
+ style: "currency",
1315
+ currency,
1316
+ minimumFractionDigits: scale,
1317
+ maximumFractionDigits: scale
1318
+ });
1319
+ return fmt.format(decimal);
1320
+ }
1321
+ function moneyScaledValue(stored, desc) {
1322
+ let raw;
1323
+ if (desc.mode === "fixed") {
1324
+ raw = stored;
1325
+ } else {
1326
+ if (!isMoneyValueObject2(stored)) return null;
1327
+ raw = stored.amount;
1328
+ }
1329
+ if (typeof raw !== "string" && typeof raw !== "number") return null;
1330
+ try {
1331
+ return BigInt(String(raw));
1332
+ } catch {
1333
+ return null;
1334
+ }
1335
+ }
1336
+ function decodeValue(stored, desc) {
1337
+ let currency;
1338
+ let scaledIntString;
1339
+ if (desc.mode === "fixed") {
1340
+ if (typeof stored !== "string" && typeof stored !== "number") return null;
1341
+ currency = desc.fixedCurrency;
1342
+ scaledIntString = String(stored);
1343
+ } else {
1344
+ if (!isMoneyValueObject2(stored)) return null;
1345
+ const amount = stored.amount;
1346
+ if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
1347
+ currency = stored.currency;
1348
+ scaledIntString = String(amount);
1349
+ }
1350
+ const scale = desc.scaleFor(currency);
1351
+ let decimal;
1352
+ try {
1353
+ decimal = formatScaledInt(BigInt(scaledIntString), scale);
1354
+ } catch {
1355
+ return null;
1356
+ }
1357
+ return {
1358
+ decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
1359
+ decimal,
1360
+ currency,
1361
+ scale
1362
+ };
1363
+ }
1364
+ function decodeMoneyFields(record, moneyFields, locale) {
1365
+ let out = { ...record };
1366
+ const format = locale !== "raw";
1367
+ const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
1368
+ for (const [path, desc] of Object.entries(moneyFields)) {
1369
+ if (isSimpleMoneyPath(path)) {
1370
+ const stored = out[path];
1371
+ if (stored === null || stored === void 0) continue;
1372
+ const r = decodeValue(stored, desc);
1373
+ if (r === null) continue;
1374
+ out[path] = r.decoded;
1375
+ if (format) {
1376
+ out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
1377
+ out[`${path}Number`] = Number(r.decimal);
1378
+ }
1379
+ continue;
1380
+ }
1381
+ out = transformAtMoneyPath(
1382
+ out,
1383
+ path,
1384
+ parseMoneyPath(path),
1385
+ 0,
1386
+ (container, key) => {
1387
+ const stored = container[key];
1388
+ if (stored === null || stored === void 0) return;
1389
+ const r = decodeValue(stored, desc);
1390
+ if (r === null) return;
1391
+ container[key] = r.decoded;
1392
+ if (format && typeof key === "string" && !Array.isArray(container)) {
1393
+ container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
1394
+ container[`${key}Number`] = Number(r.decimal);
1395
+ }
1396
+ },
1397
+ /* lenient */
1398
+ true
1399
+ );
1400
+ }
1401
+ return out;
1402
+ }
1403
+
469
1404
  // src/query/builder.ts
470
1405
  var EMPTY_PLAN = {
471
1406
  clauses: [],
@@ -474,6 +1409,7 @@ var EMPTY_PLAN = {
474
1409
  offset: 0,
475
1410
  joins: []
476
1411
  };
1412
+ var DEFAULT_CROSS_JOIN_MAX_ROWS = 5e4;
477
1413
  var Query = class _Query {
478
1414
  source;
479
1415
  plan;
@@ -506,7 +1442,7 @@ var Query = class _Query {
506
1442
  /**
507
1443
  * @internal — clone this Query with a declared-predicate map
508
1444
  * attached. Used by the materialized-view registry to enable
509
- * `.wherePredicate(name, ctx?)` for the MV's query callback (#153).
1445
+ * `.wherePredicate(name, ctx?)` for the MV's query callback.
510
1446
  * Consumers don't call this directly.
511
1447
  */
512
1448
  _withPredicates(predicates) {
@@ -519,7 +1455,7 @@ var Query = class _Query {
519
1455
  );
520
1456
  }
521
1457
  /**
522
- * Filter by a registered deterministic predicate (#153). Requires
1458
+ * Filter by a registered deterministic predicate. Requires
523
1459
  * the Query to have been augmented with a predicates map (typically
524
1460
  * via the materialized-view registry — bare Queries constructed
525
1461
  * outside an MV throw on `.wherePredicate()`).
@@ -557,9 +1493,18 @@ var Query = class _Query {
557
1493
  this.predicates
558
1494
  );
559
1495
  }
560
- /** Add a field comparison. Multiple where() calls are AND-combined. */
1496
+ /**
1497
+ * Add a field comparison. Multiple where() calls are AND-combined.
1498
+ *
1499
+ * A declared money field compares in MAJOR units (#336): the operand
1500
+ * (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
1501
+ * quantized into stored scaled-int space at build time and evaluated
1502
+ * BigInt-exact per record. A malformed operand or a string operator
1503
+ * (`contains`/`startsWith`) throws here, at the call site.
1504
+ */
561
1505
  where(field, op, value) {
562
- const clause = { type: "field", field, op, value };
1506
+ const desc = this.source.moneyFields?.[field];
1507
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
563
1508
  return new _Query(
564
1509
  this.source,
565
1510
  { ...this.plan, clauses: [...this.plan.clauses, clause] },
@@ -625,11 +1570,16 @@ var Query = class _Query {
625
1570
  this.predicates
626
1571
  );
627
1572
  }
628
- /** Sort by a field. Subsequent calls are tie-breakers. */
629
- orderBy(field, direction = "asc") {
1573
+ /**
1574
+ * Sort by a field. Subsequent calls are tie-breakers. Pass
1575
+ * `{ by: 'label' }` to sort a `dictKey`/`staticDict` field by its resolved
1576
+ * label at the query locale instead of the stored code (#285).
1577
+ */
1578
+ orderBy(field, direction = "asc", opts) {
1579
+ const entry = opts?.by === "label" ? { field, direction, by: "label" } : { field, direction };
630
1580
  return new _Query(
631
1581
  this.source,
632
- { ...this.plan, orderBy: [...this.plan.orderBy, { field, direction }] },
1582
+ { ...this.plan, orderBy: [...this.plan.orderBy, entry] },
633
1583
  this.joinContext,
634
1584
  this.aggregateStrategy,
635
1585
  this.predicates
@@ -755,25 +1705,119 @@ var Query = class _Query {
755
1705
  this.predicates
756
1706
  );
757
1707
  }
1708
+ /**
1709
+ * Cartesian-product cross-join against `target` collection. Each result row
1710
+ * carries the original `T` fields plus `result[as]` populated from every
1711
+ * right-side row (or the filtered subset when `on:` is supplied).
1712
+ *
1713
+ * **Order matters:** `.where().crossJoin()` filters BEFORE expanding (cheaper);
1714
+ * `.crossJoin().where('alias.field', ...)` filters AFTER (required when the
1715
+ * where clause references the aliased fields).
1716
+ *
1717
+ * **Cost ceiling:** `CrossJoinTooLargeError` fires before allocation when
1718
+ * `leftRows × rightRows` (or the cumulative lateral count) exceeds the limit.
1719
+ * Default: 50,000 rows. Override per-clause with `{ maxRows: N }`.
1720
+ *
1721
+ * **`on:` shapes:**
1722
+ * - `on: (left) => TTarget[]` — subset form (most efficient)
1723
+ * - `on: (left) => (right) => boolean` — predicate form
1724
+ * - `on: { predicate: 'name' }` — MV-safe, hash-tracked form
1725
+ * (requires the Query to have been augmented via `_withPredicates`)
1726
+ *
1727
+ * Requires a JoinContext (constructed via `collection.query()`).
1728
+ */
1729
+ crossJoin(target, opts) {
1730
+ if (!this.joinContext) {
1731
+ throw new Error(
1732
+ `Query.crossJoin("${target}"): requires a join context. Use collection.query() to construct a cross-join-capable Query instead of the Query constructor directly.`
1733
+ );
1734
+ }
1735
+ let onFn;
1736
+ let onPredicateName;
1737
+ if (opts.on !== void 0) {
1738
+ if (typeof opts.on === "function") {
1739
+ onFn = opts.on;
1740
+ if (this.predicates) {
1741
+ console.warn(
1742
+ `Query.crossJoin("${target}", { on: callback }): inline on: callback inside a withMaterializedView query() disables queryHash drift detection for this cross-join. Use on: { predicate: '<name>' } to enable it.`
1743
+ );
1744
+ }
1745
+ } else {
1746
+ const predName = opts.on.predicate;
1747
+ if (!this.predicates) {
1748
+ throw new Error(
1749
+ `Query.crossJoin("${target}", { on: { predicate: "${predName}" } }): the { predicate } form requires a predicates map. Use this form inside a withMaterializedView query() callback that declares predicates: { ${predName}: { hash, fn } }.`
1750
+ );
1751
+ }
1752
+ const decl = this.predicates.get(predName);
1753
+ if (!decl) {
1754
+ throw new Error(
1755
+ `Query.crossJoin("${target}"): predicate "${predName}" not registered. Available: ${[...this.predicates.keys()].join(", ") || "(none)"}.`
1756
+ );
1757
+ }
1758
+ const as = opts.as;
1759
+ const predicateFn = decl.fn;
1760
+ onFn = (_left) => (right) => predicateFn({ ..._left, [as]: right });
1761
+ onPredicateName = predName;
1762
+ }
1763
+ }
1764
+ const clause = {
1765
+ type: "crossJoin",
1766
+ target,
1767
+ as: opts.as,
1768
+ ...onFn !== void 0 && { on: onFn },
1769
+ ...onPredicateName !== void 0 && { onPredicateName },
1770
+ ...opts.maxRows !== void 0 && { maxRows: opts.maxRows }
1771
+ };
1772
+ return new _Query(
1773
+ this.source,
1774
+ { ...this.plan, clauses: [...this.plan.clauses, clause] },
1775
+ this.joinContext,
1776
+ this.aggregateStrategy,
1777
+ this.predicates
1778
+ );
1779
+ }
758
1780
  /**
759
1781
  * Execute the plan and return the matching records. When the plan
760
1782
  * carries any join legs, they are applied after `where` / `orderBy`
761
1783
  * / `limit` / `offset` narrow the left set. See the `.join()` doc
762
1784
  * for the ordering rationale.
1785
+ *
1786
+ * `opts.locale` (#285 §3) resolves JOINED right-side i18n fields at the
1787
+ * `join` layer to that locale; without it, the owning collection's default
1788
+ * locale applies, and a locale-less query leaves joined i18n fields raw.
1789
+ * (Left/base i18n fields are resolved by `get`/`list`, not here.)
763
1790
  */
764
- toArray() {
765
- const base = executePlanWithSource(this.source, this.plan);
1791
+ toArray(opts) {
1792
+ const base = this.decodeMoney(executePlanWithSource(this.source, this.plan, this.joinContext, opts?.locale));
766
1793
  if (this.plan.joins.length === 0) return base;
767
1794
  if (!this.joinContext) {
768
1795
  throw new Error(
769
1796
  `Query.toArray(): plan carries ${this.plan.joins.length} join leg(s) but no JoinContext is attached. This usually means the Query was constructed via the raw Query constructor with a plan that had joins pre-populated. Use collection.query().join(...) instead.`
770
1797
  );
771
1798
  }
772
- return applyJoins(base, this.plan.joins, this.joinContext);
1799
+ return applyJoins(base, this.plan.joins, this.joinContext, opts?.locale);
773
1800
  }
774
- /** Return the first matching record, or null. Joins are applied. */
775
- first() {
776
- const arr = this.limit(1).toArray();
1801
+ /**
1802
+ * Decode this source's money fields on read (stored scaled-int → canonical
1803
+ * decimal), so `query().toArray()` agrees with `get()`/`sum()` on the value.
1804
+ * No-op when the source declares no money fields.
1805
+ *
1806
+ * The query layer carries no locale context, so we decode with `'raw'` —
1807
+ * canonical decimal, WITHOUT fabricating locale-formatted `<field>Formatted`
1808
+ * / `<field>Number` virtuals. Producing a guessed-locale string here would
1809
+ * just reintroduce #322's "two read paths disagree" failure on the virtual
1810
+ * field (e.g. it-IT via `get()` vs en-US here). Consumers who need formatted
1811
+ * money read through `get()`/`list()` with a locale.
1812
+ */
1813
+ decodeMoney(records) {
1814
+ const moneyFields = this.source.moneyFields;
1815
+ if (!moneyFields || Object.keys(moneyFields).length === 0) return records;
1816
+ return records.map((r) => decodeMoneyFields(r, moneyFields, "raw"));
1817
+ }
1818
+ /** Return the first matching record, or null. Joins are applied. `opts.locale` resolves joined i18n fields (#285 §3). */
1819
+ first(opts) {
1820
+ const arr = this.limit(1).toArray(opts);
777
1821
  return arr[0] ?? null;
778
1822
  }
779
1823
  /**
@@ -786,9 +1830,17 @@ var Query = class _Query {
786
1830
  * intent is purely to count.
787
1831
  */
788
1832
  count() {
1833
+ if (this.plan.clauses.some((c) => c.type === "crossJoin")) {
1834
+ if (!this.joinContext) {
1835
+ throw new Error(
1836
+ `Query.count(): plan contains crossJoin clauses but no JoinContext is attached.`
1837
+ );
1838
+ }
1839
+ return executeClausePipeline(this.source, this.plan.clauses, this.joinContext).length;
1840
+ }
789
1841
  const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
790
1842
  if (remainingClauses.length === 0) return candidates.length;
791
- return filterRecords(candidates, remainingClauses).length;
1843
+ return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
792
1844
  }
793
1845
  /**
794
1846
  * Reduce the matching records through a named set of reducers.
@@ -831,11 +1883,21 @@ var Query = class _Query {
831
1883
  * partition boundaries without an API break.
832
1884
  */
833
1885
  aggregate(spec) {
1886
+ const moneyFields = this.source.moneyFields;
1887
+ if (moneyFields) {
1888
+ spec = wrapMoneyReducers(spec, moneyFields);
1889
+ }
834
1890
  const source = this.source;
835
1891
  const clauses = this.plan.clauses;
1892
+ const joinCtx = this.joinContext;
1893
+ const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
836
1894
  const executeRecords = () => {
1895
+ if (hasCrossJoins) {
1896
+ if (!joinCtx) throw new Error("Query.aggregate(): crossJoin requires a join context");
1897
+ return executeClausePipeline(source, clauses, joinCtx);
1898
+ }
837
1899
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
838
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
1900
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
839
1901
  };
840
1902
  const upstreams = [];
841
1903
  if (source.subscribe) {
@@ -850,9 +1912,15 @@ var Query = class _Query {
850
1912
  }
851
1913
  const source = this.source;
852
1914
  const clauses = this.plan.clauses;
1915
+ const joinCtx = this.joinContext;
1916
+ const hasCrossJoins = clauses.some((c) => c.type === "crossJoin");
853
1917
  const executeRecords = () => {
1918
+ if (hasCrossJoins) {
1919
+ if (!joinCtx) throw new Error("Query.groupBy(): crossJoin requires a join context");
1920
+ return executeClausePipeline(source, clauses, joinCtx);
1921
+ }
854
1922
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
855
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
1923
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
856
1924
  };
857
1925
  const upstreams = [];
858
1926
  if (source.subscribe) {
@@ -866,13 +1934,15 @@ var Query = class _Query {
866
1934
  executeRecords,
867
1935
  field,
868
1936
  upstreams,
869
- dictLabelResolver
1937
+ dictLabelResolver,
1938
+ this.source.moneyFields
870
1939
  );
871
1940
  }
872
1941
  return this.aggregateStrategy.groupByN(
873
1942
  executeRecords,
874
1943
  fields,
875
- upstreams
1944
+ upstreams,
1945
+ this.source.moneyFields
876
1946
  );
877
1947
  }
878
1948
  /**
@@ -964,6 +2034,21 @@ var Query = class _Query {
964
2034
  }
965
2035
  }
966
2036
  }
2037
+ if (this.joinContext) {
2038
+ const subscribedCross = /* @__PURE__ */ new Set();
2039
+ for (const clause of this.plan.clauses) {
2040
+ if (clause.type !== "crossJoin") continue;
2041
+ if (subscribedCross.has(clause.target)) continue;
2042
+ subscribedCross.add(clause.target);
2043
+ const rightSource = this.joinContext.resolveSource(clause.target);
2044
+ if (rightSource?.subscribe) {
2045
+ const rightSubscribe = rightSource.subscribe.bind(rightSource);
2046
+ upstreams.push({
2047
+ subscribe: (cb) => rightSubscribe(cb)
2048
+ });
2049
+ }
2050
+ }
2051
+ }
967
2052
  return buildLiveQuery(() => this.toArray(), upstreams);
968
2053
  }
969
2054
  /**
@@ -975,11 +2060,23 @@ var Query = class _Query {
975
2060
  return serializePlan(this.plan);
976
2061
  }
977
2062
  };
978
- function executePlanWithSource(source, plan) {
979
- const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
980
- let result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses);
2063
+ function executePlanWithSource(source, plan, joinContext, locale) {
2064
+ const hasCrossJoins = plan.clauses.some((c) => c.type === "crossJoin");
2065
+ let result;
2066
+ if (hasCrossJoins) {
2067
+ if (!joinContext) {
2068
+ throw new Error(
2069
+ `Query.toArray(): plan contains crossJoin clauses but no JoinContext is attached. Use collection.query() instead of new Query() for cross-join support.`
2070
+ );
2071
+ }
2072
+ result = executeClausePipeline(source, plan.clauses, joinContext);
2073
+ } else {
2074
+ const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
2075
+ result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
2076
+ }
981
2077
  if (plan.orderBy.length > 0) {
982
- result = sortRecords(result, plan.orderBy);
2078
+ const labelMaps = buildOrderLabelMaps(plan.orderBy, joinContext, locale);
2079
+ result = sortRecords(result, plan.orderBy, source.moneyFields, labelMaps);
983
2080
  }
984
2081
  if (plan.offset > 0) {
985
2082
  result = result.slice(plan.offset);
@@ -999,6 +2096,7 @@ function candidateRecords(source, clauses) {
999
2096
  const clause = clauses[i];
1000
2097
  if (clause.type !== "field") continue;
1001
2098
  if (!indexes.has(clause.field)) continue;
2099
+ if (clause.money?.mode === "multi") continue;
1002
2100
  let ids = null;
1003
2101
  if (clause.op === "==") {
1004
2102
  ids = indexes.lookupEqual(clause.field, clause.value);
@@ -1027,6 +2125,11 @@ function materializeIds(ids, lookupById) {
1027
2125
  return out;
1028
2126
  }
1029
2127
  function executePlan(records, plan) {
2128
+ if (plan.clauses.some((c) => c.type === "crossJoin")) {
2129
+ throw new Error(
2130
+ `executePlan(): does not support crossJoin clauses. executePlan is a stateless pure function \u2014 it cannot resolve cross-join right-side collections. Use Query.toArray() (via collection.query()) instead.`
2131
+ );
2132
+ }
1030
2133
  let result = filterRecords(records, plan.clauses);
1031
2134
  if (plan.orderBy.length > 0) {
1032
2135
  result = sortRecords(result, plan.orderBy);
@@ -1039,13 +2142,20 @@ function executePlan(records, plan) {
1039
2142
  }
1040
2143
  return result;
1041
2144
  }
1042
- function filterRecords(records, clauses) {
2145
+ function fnViewDecoder(source) {
2146
+ const mf = source.moneyFields;
2147
+ if (!mf || Object.keys(mf).length === 0) return void 0;
2148
+ return (r) => decodeMoneyFields(r, mf, "raw");
2149
+ }
2150
+ function filterRecords(records, clauses, decodeForFns) {
1043
2151
  if (clauses.length === 0) return [...records];
2152
+ const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
1044
2153
  const out = [];
1045
2154
  for (const r of records) {
2155
+ const fnView = needsFnView ? decodeForFns(r) : void 0;
1046
2156
  let matches = true;
1047
2157
  for (const clause of clauses) {
1048
- if (!evaluateClause(r, clause)) {
2158
+ if (!evaluateClause(r, clause, fnView)) {
1049
2159
  matches = false;
1050
2160
  break;
1051
2161
  }
@@ -1054,17 +2164,124 @@ function filterRecords(records, clauses) {
1054
2164
  }
1055
2165
  return out;
1056
2166
  }
1057
- function sortRecords(records, orderBy) {
2167
+ function executeClausePipeline(source, clauses, joinContext) {
2168
+ let rel = [...source.snapshot()];
2169
+ let filterBatch = [];
2170
+ const decodeForFns = fnViewDecoder(source);
2171
+ for (const clause of clauses) {
2172
+ if (clause.type === "crossJoin") {
2173
+ if (filterBatch.length > 0) {
2174
+ rel = filterRecords(rel, filterBatch, decodeForFns);
2175
+ filterBatch = [];
2176
+ }
2177
+ const rightSource = joinContext.resolveSource(clause.target);
2178
+ if (!rightSource) {
2179
+ throw new CrossJoinSourceUnknownError(clause.target, joinContext.leftCollection);
2180
+ }
2181
+ rel = applyCrossJoin(rel, clause, rightSource);
2182
+ } else {
2183
+ filterBatch.push(clause);
2184
+ }
2185
+ }
2186
+ if (filterBatch.length > 0) {
2187
+ rel = filterRecords(rel, filterBatch, decodeForFns);
2188
+ }
2189
+ return rel;
2190
+ }
2191
+ function applyCrossJoin(leftRel, clause, rightSource) {
2192
+ const rightRows = rightSource.snapshot();
2193
+ const maxRows = clause.maxRows ?? DEFAULT_CROSS_JOIN_MAX_ROWS;
2194
+ const { as } = clause;
2195
+ if (!clause.on) {
2196
+ const product = leftRel.length * rightRows.length;
2197
+ if (product > maxRows) {
2198
+ throw new CrossJoinTooLargeError({ target: clause.target, expected: product, limit: maxRows });
2199
+ }
2200
+ const expanded2 = [];
2201
+ for (const left of leftRel) {
2202
+ const leftObj = left;
2203
+ for (const right of rightRows) {
2204
+ expanded2.push({ ...leftObj, [as]: right });
2205
+ }
2206
+ }
2207
+ return expanded2;
2208
+ }
2209
+ const expanded = [];
2210
+ let cumulative = 0;
2211
+ for (const left of leftRel) {
2212
+ const callbackResult = clause.on(left);
2213
+ let filteredRight;
2214
+ if (Array.isArray(callbackResult)) {
2215
+ filteredRight = callbackResult;
2216
+ } else {
2217
+ filteredRight = rightRows.filter(
2218
+ callbackResult
2219
+ );
2220
+ }
2221
+ cumulative += filteredRight.length;
2222
+ if (cumulative > maxRows) {
2223
+ throw new CrossJoinTooLargeError({
2224
+ target: clause.target,
2225
+ expected: cumulative,
2226
+ limit: maxRows
2227
+ });
2228
+ }
2229
+ const leftObj = left;
2230
+ for (const right of filteredRight) {
2231
+ expanded.push({ ...leftObj, [as]: right });
2232
+ }
2233
+ }
2234
+ return expanded;
2235
+ }
2236
+ function sortRecords(records, orderBy, moneyFields, labelMaps) {
1058
2237
  return [...records].sort((a, b) => {
1059
- for (const { field, direction } of orderBy) {
1060
- const av = readField(a, field);
1061
- const bv = readField(b, field);
1062
- const cmp = compareValues(av, bv);
2238
+ for (const { field, direction, by } of orderBy) {
2239
+ let av = readField(a, field);
2240
+ let bv = readField(b, field);
2241
+ const labelMap = by === "label" ? labelMaps?.get(field) : void 0;
2242
+ if (labelMap) {
2243
+ av = (typeof av === "string" ? labelMap.get(av) : void 0) ?? av;
2244
+ bv = (typeof bv === "string" ? labelMap.get(bv) : void 0) ?? bv;
2245
+ const cmp2 = compareValues(av, bv);
2246
+ if (cmp2 !== 0) return direction === "asc" ? cmp2 : -cmp2;
2247
+ continue;
2248
+ }
2249
+ const desc = moneyFields?.[field];
2250
+ const cmp = desc ? compareMoney(av, bv, desc) : compareValues(av, bv);
1063
2251
  if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
1064
2252
  }
1065
2253
  return 0;
1066
2254
  });
1067
2255
  }
2256
+ function buildOrderLabelMaps(orderBy, joinContext, locale) {
2257
+ if (!joinContext?.resolveDictSource) return void 0;
2258
+ const resolveDict = joinContext.resolveDictSource.bind(joinContext);
2259
+ let maps;
2260
+ for (const { field, by } of orderBy) {
2261
+ if (by !== "label") continue;
2262
+ const dictSource = resolveDict(field);
2263
+ if (!dictSource) continue;
2264
+ const loc = locale ?? dictSource.displayLocale;
2265
+ if (loc === void 0) continue;
2266
+ const codeToLabel = /* @__PURE__ */ new Map();
2267
+ for (const entry of dictSource.snapshot()) {
2268
+ const k = entry["key"];
2269
+ const labels = entry["labels"];
2270
+ const label = labels?.[loc];
2271
+ if (typeof k === "string" && typeof label === "string") codeToLabel.set(k, label);
2272
+ }
2273
+ ;
2274
+ (maps ??= /* @__PURE__ */ new Map()).set(field, codeToLabel);
2275
+ }
2276
+ return maps;
2277
+ }
2278
+ function compareMoney(a, b, desc) {
2279
+ const av = moneyScaledValue(a, desc);
2280
+ const bv = moneyScaledValue(b, desc);
2281
+ if (av === null) return bv === null ? 0 : 1;
2282
+ if (bv === null) return -1;
2283
+ return av < bv ? -1 : av > bv ? 1 : 0;
2284
+ }
1068
2285
  function readField(record, field) {
1069
2286
  if (record === null || record === void 0) return void 0;
1070
2287
  if (!field.includes(".")) {
@@ -1116,6 +2333,16 @@ function serializeClause(clause) {
1116
2333
  clauses: clause.clauses.map(serializeClause)
1117
2334
  };
1118
2335
  }
2336
+ if (clause.type === "crossJoin") {
2337
+ return {
2338
+ type: "crossJoin",
2339
+ target: clause.target,
2340
+ as: clause.as,
2341
+ on: clause.on ? "[function]" : void 0,
2342
+ onPredicateName: clause.onPredicateName,
2343
+ maxRows: clause.maxRows
2344
+ };
2345
+ }
1119
2346
  return clause;
1120
2347
  }
1121
2348
  function canonicalCtxHash(ctx) {
@@ -1141,6 +2368,7 @@ function buildDictLabelResolver(joinCtx, field) {
1141
2368
  const dictSource = joinCtx.resolveDictSource(field);
1142
2369
  if (!dictSource) return void 0;
1143
2370
  const snapshot = dictSource.snapshot();
2371
+ const displayLocale = dictSource.displayLocale;
1144
2372
  const dictMap = /* @__PURE__ */ new Map();
1145
2373
  for (const entry of snapshot) {
1146
2374
  const k = entry["key"];
@@ -1150,9 +2378,11 @@ function buildDictLabelResolver(joinCtx, field) {
1150
2378
  }
1151
2379
  }
1152
2380
  return async (key, locale, fallback) => {
2381
+ const effLocale = locale || displayLocale;
2382
+ if (!effLocale) return void 0;
1153
2383
  const labels = dictMap.get(key);
1154
2384
  if (!labels) return void 0;
1155
- if (labels[locale] !== void 0) return labels[locale];
2385
+ if (labels[effLocale] !== void 0) return labels[effLocale];
1156
2386
  const chain = Array.isArray(fallback) ? fallback : fallback ? [fallback] : [];
1157
2387
  for (const fb of chain) {
1158
2388
  if (fb === "any") {
@@ -1295,26 +2525,37 @@ function count(opts) {
1295
2525
  const _seed = opts?.seed;
1296
2526
  void _seed;
1297
2527
  return {
2528
+ op: "count",
1298
2529
  init: () => 0,
1299
2530
  step: (state) => state + 1,
1300
2531
  remove: (state) => state - 1,
1301
- finalize: (state) => state
2532
+ finalize: (state) => state,
2533
+ merge: (a, b) => a + b
1302
2534
  };
1303
2535
  }
1304
2536
  function sum(field, opts) {
1305
2537
  const _seed = opts?.seed;
1306
2538
  void _seed;
1307
2539
  return {
2540
+ op: "sum",
2541
+ field,
2542
+ // Money-only metadata, read by `wrapMoneyReducers`. No effect on a
2543
+ // generic numeric sum.
2544
+ ...opts?.convertTo !== void 0 ? { convertTo: opts.convertTo } : {},
2545
+ ...opts?.fx !== void 0 ? { fx: opts.fx } : {},
1308
2546
  init: () => 0,
1309
2547
  step: (state, record) => state + readNumber(record, field),
1310
2548
  remove: (state, record) => state - readNumber(record, field),
1311
- finalize: (state) => state
2549
+ finalize: (state) => state,
2550
+ merge: (a, b) => a + b
1312
2551
  };
1313
2552
  }
1314
2553
  function avg(field, opts) {
1315
2554
  const _seed = opts?.seed;
1316
2555
  void _seed;
1317
2556
  return {
2557
+ op: "avg",
2558
+ field,
1318
2559
  init: () => ({ sum: 0, count: 0 }),
1319
2560
  step: (state, record) => ({
1320
2561
  sum: state.sum + readNumber(record, field),
@@ -1324,7 +2565,8 @@ function avg(field, opts) {
1324
2565
  sum: state.sum - readNumber(record, field),
1325
2566
  count: state.count - 1
1326
2567
  }),
1327
- finalize: (state) => state.count === 0 ? null : state.sum / state.count
2568
+ finalize: (state) => state.count === 0 ? null : state.sum / state.count,
2569
+ merge: (a, b) => ({ sum: a.sum + b.sum, count: a.count + b.count })
1328
2570
  };
1329
2571
  }
1330
2572
  function pushValue(state, value) {
@@ -1341,6 +2583,8 @@ function min(field, opts) {
1341
2583
  const _seed = opts?.seed;
1342
2584
  void _seed;
1343
2585
  return {
2586
+ op: "min",
2587
+ field,
1344
2588
  init: () => ({ values: [] }),
1345
2589
  step: (state, record) => pushValue(state, readNumber(record, field)),
1346
2590
  remove: (state, record) => removeValue(state, readNumber(record, field)),
@@ -1352,13 +2596,16 @@ function min(field, opts) {
1352
2596
  if (v < out) out = v;
1353
2597
  }
1354
2598
  return out;
1355
- }
2599
+ },
2600
+ merge: (a, b) => ({ values: [...a.values, ...b.values] })
1356
2601
  };
1357
2602
  }
1358
2603
  function max(field, opts) {
1359
2604
  const _seed = opts?.seed;
1360
2605
  void _seed;
1361
2606
  return {
2607
+ op: "max",
2608
+ field,
1362
2609
  init: () => ({ values: [] }),
1363
2610
  step: (state, record) => pushValue(state, readNumber(record, field)),
1364
2611
  remove: (state, record) => removeValue(state, readNumber(record, field)),
@@ -1370,11 +2617,17 @@ function max(field, opts) {
1370
2617
  if (v > out) out = v;
1371
2618
  }
1372
2619
  return out;
1373
- }
2620
+ },
2621
+ merge: (a, b) => ({ values: [...a.values, ...b.values] })
1374
2622
  };
1375
2623
  }
1376
2624
  function readNumber(record, field) {
1377
2625
  const value = readPath(record, field);
2626
+ if (typeof value === "object" && value !== null && "amount" in value && "currency" in value) {
2627
+ throw new Error(
2628
+ `aggregate: field "${field}" holds a money value but was not money-aware \u2014 declare it in the collection's moneyFields so sum/min/max stay exact`
2629
+ );
2630
+ }
1378
2631
  return typeof value === "number" && Number.isFinite(value) ? value : 0;
1379
2632
  }
1380
2633
 
@@ -1530,15 +2783,17 @@ function resetGroupByWarnings() {
1530
2783
  warnedCardinalityFields.clear();
1531
2784
  }
1532
2785
  var GroupedQueryBase = class {
1533
- constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver) {
2786
+ constructor(executeRecords, fieldOrFields, upstreams, dictLabelResolver, moneyFields) {
1534
2787
  this.executeRecords = executeRecords;
1535
2788
  this.upstreams = upstreams;
1536
2789
  this.dictLabelResolver = dictLabelResolver;
2790
+ this.moneyFields = moneyFields;
1537
2791
  this.fields = typeof fieldOrFields === "string" ? [fieldOrFields] : [...fieldOrFields];
1538
2792
  }
1539
2793
  executeRecords;
1540
2794
  upstreams;
1541
2795
  dictLabelResolver;
2796
+ moneyFields;
1542
2797
  /**
1543
2798
  * Field set this grouped query buckets on. Stored in declaration
1544
2799
  * order — the same order is preserved on every result row by
@@ -1546,6 +2801,10 @@ var GroupedQueryBase = class {
1546
2801
  * `[field]`.
1547
2802
  */
1548
2803
  fields;
2804
+ /** Apply money-aware reducer rewriting when money fields are declared. */
2805
+ wrapSpec(spec) {
2806
+ return this.moneyFields ? wrapMoneyReducers(spec, this.moneyFields) : spec;
2807
+ }
1549
2808
  };
1550
2809
  var GroupedQuery = class extends GroupedQueryBase {
1551
2810
  /**
@@ -1558,7 +2817,7 @@ var GroupedQuery = class extends GroupedQueryBase {
1558
2817
  return new GroupedAggregation(
1559
2818
  this.executeRecords,
1560
2819
  this.fields,
1561
- spec,
2820
+ this.wrapSpec(spec),
1562
2821
  this.upstreams,
1563
2822
  this.dictLabelResolver
1564
2823
  );
@@ -1569,17 +2828,20 @@ var GroupedQueryN = class extends GroupedQueryBase {
1569
2828
  return new GroupedAggregation(
1570
2829
  this.executeRecords,
1571
2830
  this.fields,
1572
- spec,
2831
+ this.wrapSpec(spec),
1573
2832
  this.upstreams,
1574
2833
  this.dictLabelResolver
1575
2834
  );
1576
2835
  }
1577
2836
  };
1578
- function groupAndReduce(records, fieldOrFields, spec) {
2837
+ function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
1579
2838
  const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
1580
2839
  if (fields.length === 0) {
1581
2840
  throw new Error(".groupBy() requires at least one field");
1582
2841
  }
2842
+ if (moneyFields) {
2843
+ spec = wrapMoneyReducers(spec, moneyFields);
2844
+ }
1583
2845
  const buckets = /* @__PURE__ */ new Map();
1584
2846
  const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
1585
2847
  for (const record of records) {
@@ -1641,9 +2903,29 @@ var GroupedAggregation = class {
1641
2903
  upstreams;
1642
2904
  dictLabelResolver;
1643
2905
  fields;
1644
- /** Execute the query, group, reduce, and return an array of rows. */
1645
- run() {
1646
- return groupAndReduce(this.executeRecords(), this.fields, this.spec);
2906
+ /**
2907
+ * Execute the query, group, reduce, and return an array of rows.
2908
+ *
2909
+ * `opts` (#285 query-form MV grouping): when a `locale` + `i18nFields` are
2910
+ * given, the declared group-key `i18nText` fields are resolved to that locale
2911
+ * at the `mv` layer BEFORE bucketing — so an i18n group key is a stable string
2912
+ * instead of a raw `{locale}` map. The MV executor passes the MV's
2913
+ * `i18nLocale`/`i18nFields`; ordinary `.run()` callers pass nothing and are
2914
+ * unaffected.
2915
+ */
2916
+ run(opts) {
2917
+ let records = this.executeRecords();
2918
+ if (opts?.locale !== void 0 && opts.i18nFields !== void 0) {
2919
+ const groupI18n = {};
2920
+ for (const f of this.fields) {
2921
+ const d = opts.i18nFields[f];
2922
+ if (d !== void 0) groupI18n[f] = d;
2923
+ }
2924
+ if (Object.keys(groupI18n).length > 0) {
2925
+ records = records.map((r) => applyI18nLocale(r, groupI18n, opts.locale, void 0, "mv"));
2926
+ }
2927
+ }
2928
+ return groupAndReduce(records, this.fields, this.spec);
1647
2929
  }
1648
2930
  /**
1649
2931
  * Execute the query, group, reduce, and resolve `<field>Label` for
@@ -1723,12 +3005,29 @@ var ScanBuilder = class _ScanBuilder {
1723
3005
  * context throws with an actionable error.
1724
3006
  */
1725
3007
  joinContext;
1726
- constructor(pageProvider, pageSize = DEFAULT_SCAN_PAGE_SIZE, clauses = [], joins = [], joinContext) {
3008
+ /**
3009
+ * Money field descriptors for the backing collection. When present, yielded
3010
+ * records are decoded (stored scaled-int → canonical decimal) so `scan()`
3011
+ * agrees with `get()`/`list()`/`query().toArray()` — #322. Decoded with
3012
+ * `'raw'` (canonical decimal, no locale-formatted virtuals) since the scan
3013
+ * stream carries no locale context, mirroring `Query.toArray()`.
3014
+ */
3015
+ moneyFields;
3016
+ constructor(pageProvider, pageSize = DEFAULT_SCAN_PAGE_SIZE, clauses = [], joins = [], joinContext, moneyFields) {
1727
3017
  this.pageProvider = pageProvider;
1728
3018
  this.pageSize = pageSize;
1729
3019
  this.clauses = clauses;
1730
3020
  this.joins = joins;
1731
3021
  this.joinContext = joinContext;
3022
+ this.moneyFields = moneyFields;
3023
+ }
3024
+ /**
3025
+ * Decode this scan's money fields on a record (stored scaled-int → canonical
3026
+ * decimal). No-op when no money fields are declared. See {@link moneyFields}.
3027
+ */
3028
+ decodeMoney(record) {
3029
+ if (!this.moneyFields || Object.keys(this.moneyFields).length === 0) return record;
3030
+ return decodeMoneyFields(record, this.moneyFields, "raw");
1732
3031
  }
1733
3032
  /**
1734
3033
  * Add a field comparison. Runs per record as the scan stream
@@ -1744,13 +3043,15 @@ var ScanBuilder = class _ScanBuilder {
1744
3043
  * evaluates clauses per record in O(1) per clause.
1745
3044
  */
1746
3045
  where(field, op, value) {
1747
- const clause = { type: "field", field, op, value };
3046
+ const desc = this.moneyFields?.[field];
3047
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
1748
3048
  return new _ScanBuilder(
1749
3049
  this.pageProvider,
1750
3050
  this.pageSize,
1751
3051
  [...this.clauses, clause],
1752
3052
  this.joins,
1753
- this.joinContext
3053
+ this.joinContext,
3054
+ this.moneyFields
1754
3055
  );
1755
3056
  }
1756
3057
  /**
@@ -1769,7 +3070,8 @@ var ScanBuilder = class _ScanBuilder {
1769
3070
  this.pageSize,
1770
3071
  [...this.clauses, clause],
1771
3072
  this.joins,
1772
- this.joinContext
3073
+ this.joinContext,
3074
+ this.moneyFields
1773
3075
  );
1774
3076
  }
1775
3077
  /**
@@ -1880,7 +3182,8 @@ var ScanBuilder = class _ScanBuilder {
1880
3182
  this.pageSize,
1881
3183
  this.clauses,
1882
3184
  [...this.joins, leg],
1883
- this.joinContext
3185
+ this.joinContext,
3186
+ this.moneyFields
1884
3187
  );
1885
3188
  }
1886
3189
  /**
@@ -1897,10 +3200,11 @@ var ScanBuilder = class _ScanBuilder {
1897
3200
  while (true) {
1898
3201
  for (const record of page.items) {
1899
3202
  if (!this.recordMatches(record)) continue;
3203
+ const decoded = this.decodeMoney(record);
1900
3204
  if (joinResolvers === null) {
1901
- yield record;
3205
+ yield decoded;
1902
3206
  } else {
1903
- let attached = record;
3207
+ let attached = decoded;
1904
3208
  for (const resolver of joinResolvers) {
1905
3209
  attached = this.applyOneJoinStreaming(attached, resolver);
1906
3210
  }
@@ -2086,8 +3390,9 @@ var ScanBuilder = class _ScanBuilder {
2086
3390
  */
2087
3391
  recordMatches(record) {
2088
3392
  if (this.clauses.length === 0) return true;
3393
+ const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
2089
3394
  for (const clause of this.clauses) {
2090
- if (!evaluateClause(record, clause)) return false;
3395
+ if (!evaluateClause(record, clause, fnView)) return false;
2091
3396
  }
2092
3397
  return true;
2093
3398
  }
@@ -2102,6 +3407,9 @@ function coerceRefKey2(value) {
2102
3407
  0 && (module.exports = {
2103
3408
  Aggregation,
2104
3409
  CollectionIndexes,
3410
+ CrossJoinSourceUnknownError,
3411
+ CrossJoinTooLargeError,
3412
+ DEFAULT_CROSS_JOIN_MAX_ROWS,
2105
3413
  DEFAULT_JOIN_MAX_ROWS,
2106
3414
  DanglingReferenceError,
2107
3415
  GROUPBY_MAX_CARDINALITY,