@noy-db/hub 0.2.0-pre.14 → 0.2.0-pre.16

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 (219) hide show
  1. package/dist/aggregate/index.cjs +160 -64
  2. package/dist/aggregate/index.cjs.map +1 -1
  3. package/dist/aggregate/index.d.cts +2 -2
  4. package/dist/aggregate/index.d.ts +2 -2
  5. package/dist/aggregate/index.js +3 -3
  6. package/dist/attestation/index.cjs.map +1 -1
  7. package/dist/attestation/index.d.cts +5 -5
  8. package/dist/attestation/index.d.ts +5 -5
  9. package/dist/attestation/index.js +4 -4
  10. package/dist/blobs/index.cjs.map +1 -1
  11. package/dist/blobs/index.d.cts +6 -6
  12. package/dist/blobs/index.d.ts +6 -6
  13. package/dist/blobs/index.js +3 -3
  14. package/dist/bundle/index.cjs +607 -114
  15. package/dist/bundle/index.cjs.map +1 -1
  16. package/dist/bundle/index.d.cts +7 -7
  17. package/dist/bundle/index.d.ts +7 -7
  18. package/dist/bundle/index.js +7 -7
  19. package/dist/{chunk-BIYRQQV6.js → chunk-3YWP3WBP.js} +3 -3
  20. package/dist/{chunk-VU7SWWT5.js → chunk-42FEUPZQ.js} +10 -6
  21. package/dist/chunk-42FEUPZQ.js.map +1 -0
  22. package/dist/{chunk-ACKFRSAH.js → chunk-667MB6AH.js} +132 -54
  23. package/dist/chunk-667MB6AH.js.map +1 -0
  24. package/dist/{chunk-A5ZOOZFB.js → chunk-6H2ZUNR7.js} +2 -2
  25. package/dist/{chunk-7HT2MEZ5.js → chunk-7BQ4QWYX.js} +3 -3
  26. package/dist/{chunk-DQU36Q7I.js → chunk-7Z7KSVA5.js} +13 -4
  27. package/dist/chunk-7Z7KSVA5.js.map +1 -0
  28. package/dist/{chunk-WBAYSNUQ.js → chunk-BI6ETQPF.js} +2 -2
  29. package/dist/{chunk-56DJ7JVK.js → chunk-BR3AMFGS.js} +2 -2
  30. package/dist/chunk-CJORTUJ2.js +524 -0
  31. package/dist/chunk-CJORTUJ2.js.map +1 -0
  32. package/dist/{chunk-YNTBADIY.js → chunk-CZI2A4MQ.js} +2 -2
  33. package/dist/{chunk-COFPAMX6.js → chunk-DLZ2ONOD.js} +3 -3
  34. package/dist/{chunk-KGCORI4L.js → chunk-DUREQF5W.js} +266 -66
  35. package/dist/chunk-DUREQF5W.js.map +1 -0
  36. package/dist/{chunk-PE4AQGFH.js → chunk-E2CDVKMH.js} +3 -3
  37. package/dist/{chunk-GC4V7RU7.js → chunk-F3BPIPLS.js} +1 -1
  38. package/dist/{chunk-GC4V7RU7.js.map → chunk-F3BPIPLS.js.map} +1 -1
  39. package/dist/{chunk-L2FE64BU.js → chunk-FFXM3ZIF.js} +2 -2
  40. package/dist/{chunk-5LQG6ZO2.js → chunk-G4SCICH5.js} +8 -3
  41. package/dist/chunk-G4SCICH5.js.map +1 -0
  42. package/dist/{chunk-WGHU7BLI.js → chunk-GNI5STXQ.js} +2 -2
  43. package/dist/{chunk-UWNYBOOO.js → chunk-HBXJ37ZY.js} +11 -5
  44. package/dist/chunk-HBXJ37ZY.js.map +1 -0
  45. package/dist/{chunk-NP6EZT44.js → chunk-IQLVUT37.js} +2 -2
  46. package/dist/{chunk-7PS7EOCF.js → chunk-IXBIFDEW.js} +2 -2
  47. package/dist/{chunk-LX3CB26H.js → chunk-KABJXG2F.js} +2 -2
  48. package/dist/{chunk-3EWA37FV.js → chunk-L2BNJ6HM.js} +32 -276
  49. package/dist/chunk-L2BNJ6HM.js.map +1 -0
  50. package/dist/{chunk-DKO2QFSA.js → chunk-OB2ZJQ2D.js} +2 -2
  51. package/dist/{chunk-4PEFEETV.js → chunk-OMAMZKKD.js} +2 -2
  52. package/dist/{chunk-EGD5DXFT.js → chunk-OQSRJG6A.js} +13 -1
  53. package/dist/chunk-OQSRJG6A.js.map +1 -0
  54. package/dist/{chunk-KI6HAJWL.js → chunk-QSUK7YWK.js} +2 -2
  55. package/dist/{chunk-YHPM5D7Y.js → chunk-QVIEAYTP.js} +61 -2
  56. package/dist/chunk-QVIEAYTP.js.map +1 -0
  57. package/dist/{chunk-NSCVNK5K.js → chunk-SCJPI4Z5.js} +3 -3
  58. package/dist/{chunk-ZWTNWAO4.js → chunk-TKIY625R.js} +13 -3
  59. package/dist/chunk-TKIY625R.js.map +1 -0
  60. package/dist/{chunk-OHVFWCJP.js → chunk-VLMPU56Q.js} +48 -18
  61. package/dist/chunk-VLMPU56Q.js.map +1 -0
  62. package/dist/{chunk-6AJBSQU4.js → chunk-XL35NSEN.js} +2 -2
  63. package/dist/{chunk-WIBHRONM.js → chunk-XWH4MXIU.js} +2 -2
  64. package/dist/consent/index.d.cts +6 -6
  65. package/dist/consent/index.d.ts +6 -6
  66. package/dist/derivations/index.cjs +24 -3
  67. package/dist/derivations/index.cjs.map +1 -1
  68. package/dist/derivations/index.d.cts +7 -7
  69. package/dist/derivations/index.d.ts +7 -7
  70. package/dist/derivations/index.js +2 -2
  71. package/dist/{dev-unlock-BF4OSxRv.d.cts → dev-unlock-8XzcD2Z4.d.cts} +1 -1
  72. package/dist/{dev-unlock-DV7ujTCI.d.ts → dev-unlock-DR3upLd1.d.ts} +1 -1
  73. package/dist/executor-AZLS3KBK.js +11 -0
  74. package/dist/{fanout-sidecar-N6OJX6QR.js → fanout-sidecar-67CMI3UT.js} +2 -2
  75. package/dist/guards/index.cjs +9 -5
  76. package/dist/guards/index.cjs.map +1 -1
  77. package/dist/guards/index.d.cts +7 -7
  78. package/dist/guards/index.d.ts +7 -7
  79. package/dist/guards/index.js +1 -1
  80. package/dist/{hash-DswxkLtW.d.ts → hash-CDjye9KV.d.ts} +1 -1
  81. package/dist/{hash-BcF5WQXl.d.cts → hash-DuQ88_5W.d.cts} +1 -1
  82. package/dist/history/index.cjs.map +1 -1
  83. package/dist/history/index.d.cts +7 -7
  84. package/dist/history/index.d.ts +7 -7
  85. package/dist/history/index.js +2 -2
  86. package/dist/i18n/index.cjs.map +1 -1
  87. package/dist/i18n/index.d.cts +6 -6
  88. package/dist/i18n/index.d.ts +6 -6
  89. package/dist/i18n/index.js +3 -3
  90. package/dist/{immutable-guard-7KqslW2K.d.cts → immutable-guard-CRPvu24K.d.cts} +16 -1
  91. package/dist/{immutable-guard-C8IYdzfu.d.ts → immutable-guard-Dov3WvwF.d.ts} +16 -1
  92. package/dist/{index-Cqzp4tt9.d.ts → index-C8Bk3-VF.d.cts} +11 -3
  93. package/dist/{index-CUVOMtgg.d.cts → index-nP99bXLg.d.ts} +11 -3
  94. package/dist/index.cjs +840 -122
  95. package/dist/index.cjs.map +1 -1
  96. package/dist/index.d.cts +146 -15
  97. package/dist/index.d.ts +146 -15
  98. package/dist/index.js +153 -35
  99. package/dist/index.js.map +1 -1
  100. package/dist/indexing/index.cjs +92 -31
  101. package/dist/indexing/index.cjs.map +1 -1
  102. package/dist/indexing/index.d.cts +3 -3
  103. package/dist/indexing/index.d.ts +3 -3
  104. package/dist/indexing/index.js +3 -3
  105. package/dist/{issue-ADVS4OVP.js → issue-RZP3VI6O.js} +4 -4
  106. package/dist/{lazy-builder-D5GU14TS.d.ts → lazy-builder-ChSqcF5t.d.ts} +1 -1
  107. package/dist/{lazy-builder-Ci5_YG73.d.cts → lazy-builder-eYZzLEL1.d.cts} +1 -1
  108. package/dist/{ledger-CWSE3BLF.js → ledger-A3LL253R.js} +3 -3
  109. package/dist/materialized-views/index.cjs +409 -7
  110. package/dist/materialized-views/index.cjs.map +1 -1
  111. package/dist/materialized-views/index.d.cts +7 -7
  112. package/dist/materialized-views/index.d.ts +7 -7
  113. package/dist/materialized-views/index.js +6 -6
  114. package/dist/noydb-WCMY2ZOW.js +35 -0
  115. package/dist/overlay-views/index.cjs +47 -17
  116. package/dist/overlay-views/index.cjs.map +1 -1
  117. package/dist/overlay-views/index.d.cts +28 -10
  118. package/dist/overlay-views/index.d.ts +28 -10
  119. package/dist/overlay-views/index.js +1 -1
  120. package/dist/periods/index.cjs.map +1 -1
  121. package/dist/periods/index.d.cts +6 -6
  122. package/dist/periods/index.d.ts +6 -6
  123. package/dist/periods/index.js +3 -3
  124. package/dist/{predicate-Bt5ft-9c.d.cts → predicate-BmhBSPCH.d.cts} +59 -2
  125. package/dist/{predicate-Bt5ft-9c.d.ts → predicate-BmhBSPCH.d.ts} +59 -2
  126. package/dist/{public-envelope-SYHEYQ3X.js → public-envelope-YP2UWMLG.js} +3 -3
  127. package/dist/query/index.cjs +604 -205
  128. package/dist/query/index.cjs.map +1 -1
  129. package/dist/query/index.d.cts +3 -3
  130. package/dist/query/index.d.ts +3 -3
  131. package/dist/query/index.js +5 -5
  132. package/dist/{registry-XGLNADIE.js → registry-EB6SISTA.js} +2 -2
  133. package/dist/{registry-DK5YWAAA.js → registry-UTA4CLQS.js} +2 -2
  134. package/dist/{revoke-ZDFKMR5E.js → revoke-HNMQZSCL.js} +4 -4
  135. package/dist/session/index.d.cts +7 -7
  136. package/dist/session/index.d.ts +7 -7
  137. package/dist/shadow/index.d.cts +6 -6
  138. package/dist/shadow/index.d.ts +6 -6
  139. package/dist/{signer-P5D7Y72U.js → signer-DCMNKXSF.js} +3 -3
  140. package/dist/snapshots/index.d.cts +6 -6
  141. package/dist/snapshots/index.d.ts +6 -6
  142. package/dist/snapshots/index.js +3 -3
  143. package/dist/{stale-7FRJVHN6.js → stale-W5PQTRYH.js} +2 -2
  144. package/dist/store/index.d.cts +6 -6
  145. package/dist/store/index.d.ts +6 -6
  146. package/dist/{strategy-CrS7PnbE.d.cts → strategy-BtW8fAjz.d.cts} +2 -2
  147. package/dist/{strategy-CrS7PnbE.d.ts → strategy-BtW8fAjz.d.ts} +2 -2
  148. package/dist/sync/index.cjs.map +1 -1
  149. package/dist/sync/index.d.cts +5 -5
  150. package/dist/sync/index.d.ts +5 -5
  151. package/dist/sync/index.js +2 -2
  152. package/dist/team/index.cjs.map +1 -1
  153. package/dist/team/index.d.cts +6 -6
  154. package/dist/team/index.d.ts +6 -6
  155. package/dist/team/index.js +5 -5
  156. package/dist/tx/index.cjs +66 -3
  157. package/dist/tx/index.cjs.map +1 -1
  158. package/dist/tx/index.d.cts +24 -8
  159. package/dist/tx/index.d.ts +24 -8
  160. package/dist/tx/index.js +7 -3
  161. package/dist/tx/index.js.map +1 -1
  162. package/dist/{types-V5R2-pd4.d.cts → types-Bze6vkwm.d.cts} +391 -144
  163. package/dist/{types-BFHQUjdy.d.ts → types-DrmBTscX.d.ts} +391 -144
  164. package/dist/{ulid-p2nKiiKg.d.ts → ulid-DbBVrNSt.d.ts} +1 -1
  165. package/dist/{ulid-CwNf9e6-.d.cts → ulid-DfZlAh0u.d.cts} +1 -1
  166. package/dist/{vault-group-W7QC4UYW.js → vault-group-DX2HFQMX.js} +3 -3
  167. package/dist/{with-derivation-C9K43BOB.d.cts → with-derivation-CCqAchD5.d.cts} +1 -1
  168. package/dist/{with-derivation-Ds9yZgCj.d.ts → with-derivation-_lySGdlm.d.ts} +1 -1
  169. package/dist/{with-materialized-view-DgQcAjYv.d.cts → with-materialized-view--4PsvMDu.d.cts} +1 -1
  170. package/dist/{with-materialized-view-DwR4jkV5.d.ts → with-materialized-view-QT1Tp7NO.d.ts} +1 -1
  171. package/dist/{with-overlayed-view-ByyhHdVr.d.ts → with-overlayed-view-BEXfpzSb.d.ts} +1 -1
  172. package/dist/{with-overlayed-view-7-rUB3vD.d.cts → with-overlayed-view-DlH5qmeB.d.cts} +1 -1
  173. package/package.json +3 -3
  174. package/dist/chunk-3EWA37FV.js.map +0 -1
  175. package/dist/chunk-5LQG6ZO2.js.map +0 -1
  176. package/dist/chunk-ACKFRSAH.js.map +0 -1
  177. package/dist/chunk-DQU36Q7I.js.map +0 -1
  178. package/dist/chunk-EGD5DXFT.js.map +0 -1
  179. package/dist/chunk-KGCORI4L.js.map +0 -1
  180. package/dist/chunk-OHVFWCJP.js.map +0 -1
  181. package/dist/chunk-TV3YZ35S.js +0 -90
  182. package/dist/chunk-TV3YZ35S.js.map +0 -1
  183. package/dist/chunk-UWNYBOOO.js.map +0 -1
  184. package/dist/chunk-VU7SWWT5.js.map +0 -1
  185. package/dist/chunk-YHPM5D7Y.js.map +0 -1
  186. package/dist/chunk-ZWTNWAO4.js.map +0 -1
  187. package/dist/executor-723ZP6TH.js +0 -11
  188. package/dist/noydb-VZ4JVW55.js +0 -35
  189. /package/dist/{chunk-BIYRQQV6.js.map → chunk-3YWP3WBP.js.map} +0 -0
  190. /package/dist/{chunk-A5ZOOZFB.js.map → chunk-6H2ZUNR7.js.map} +0 -0
  191. /package/dist/{chunk-7HT2MEZ5.js.map → chunk-7BQ4QWYX.js.map} +0 -0
  192. /package/dist/{chunk-WBAYSNUQ.js.map → chunk-BI6ETQPF.js.map} +0 -0
  193. /package/dist/{chunk-56DJ7JVK.js.map → chunk-BR3AMFGS.js.map} +0 -0
  194. /package/dist/{chunk-YNTBADIY.js.map → chunk-CZI2A4MQ.js.map} +0 -0
  195. /package/dist/{chunk-COFPAMX6.js.map → chunk-DLZ2ONOD.js.map} +0 -0
  196. /package/dist/{chunk-PE4AQGFH.js.map → chunk-E2CDVKMH.js.map} +0 -0
  197. /package/dist/{chunk-L2FE64BU.js.map → chunk-FFXM3ZIF.js.map} +0 -0
  198. /package/dist/{chunk-WGHU7BLI.js.map → chunk-GNI5STXQ.js.map} +0 -0
  199. /package/dist/{chunk-NP6EZT44.js.map → chunk-IQLVUT37.js.map} +0 -0
  200. /package/dist/{chunk-7PS7EOCF.js.map → chunk-IXBIFDEW.js.map} +0 -0
  201. /package/dist/{chunk-LX3CB26H.js.map → chunk-KABJXG2F.js.map} +0 -0
  202. /package/dist/{chunk-DKO2QFSA.js.map → chunk-OB2ZJQ2D.js.map} +0 -0
  203. /package/dist/{chunk-4PEFEETV.js.map → chunk-OMAMZKKD.js.map} +0 -0
  204. /package/dist/{chunk-KI6HAJWL.js.map → chunk-QSUK7YWK.js.map} +0 -0
  205. /package/dist/{chunk-NSCVNK5K.js.map → chunk-SCJPI4Z5.js.map} +0 -0
  206. /package/dist/{chunk-6AJBSQU4.js.map → chunk-XL35NSEN.js.map} +0 -0
  207. /package/dist/{chunk-WIBHRONM.js.map → chunk-XWH4MXIU.js.map} +0 -0
  208. /package/dist/{executor-723ZP6TH.js.map → executor-AZLS3KBK.js.map} +0 -0
  209. /package/dist/{fanout-sidecar-N6OJX6QR.js.map → fanout-sidecar-67CMI3UT.js.map} +0 -0
  210. /package/dist/{issue-ADVS4OVP.js.map → issue-RZP3VI6O.js.map} +0 -0
  211. /package/dist/{ledger-CWSE3BLF.js.map → ledger-A3LL253R.js.map} +0 -0
  212. /package/dist/{noydb-VZ4JVW55.js.map → noydb-WCMY2ZOW.js.map} +0 -0
  213. /package/dist/{public-envelope-SYHEYQ3X.js.map → public-envelope-YP2UWMLG.js.map} +0 -0
  214. /package/dist/{registry-DK5YWAAA.js.map → registry-EB6SISTA.js.map} +0 -0
  215. /package/dist/{registry-XGLNADIE.js.map → registry-UTA4CLQS.js.map} +0 -0
  216. /package/dist/{revoke-ZDFKMR5E.js.map → revoke-HNMQZSCL.js.map} +0 -0
  217. /package/dist/{signer-P5D7Y72U.js.map → signer-DCMNKXSF.js.map} +0 -0
  218. /package/dist/{stale-7FRJVHN6.js.map → stale-W5PQTRYH.js.map} +0 -0
  219. /package/dist/{vault-group-W7QC4UYW.js.map → vault-group-DX2HFQMX.js.map} +0 -0
package/dist/index.cjs CHANGED
@@ -2397,6 +2397,30 @@ function parseToScaledInt(input, scale, rounding) {
2397
2397
  }
2398
2398
  return { ok: true, value: negative && magnitude !== 0n ? -magnitude : magnitude };
2399
2399
  }
2400
+ function decimalScaleOf(input) {
2401
+ const s = toCanonicalDecimalString(input);
2402
+ if (s === null) return null;
2403
+ const dot = s.indexOf(".");
2404
+ return dot === -1 ? 0 : s.length - dot - 1;
2405
+ }
2406
+ function rescaleScaledInt(value, fromScale, toScale, rounding = "half-up") {
2407
+ if (toScale >= fromScale) return value * 10n ** BigInt(toScale - fromScale);
2408
+ const drop = fromScale - toScale;
2409
+ const negative = value < 0n;
2410
+ const absStr = (negative ? -value : value).toString().padStart(drop + 1, "0");
2411
+ const keptStr = absStr.slice(0, absStr.length - drop);
2412
+ const tail = absStr.slice(absStr.length - drop);
2413
+ let magnitude = BigInt(keptStr);
2414
+ if (!/^0+$/.test(tail)) {
2415
+ const lastKeptDigit = Number(keptStr[keptStr.length - 1]);
2416
+ const firstDiscarded = Number(tail[0]);
2417
+ const hasMoreNonZeroAfterFirst = /[1-9]/.test(tail.slice(1));
2418
+ if (shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, rounding)) {
2419
+ magnitude += 1n;
2420
+ }
2421
+ }
2422
+ return negative && magnitude !== 0n ? -magnitude : magnitude;
2423
+ }
2400
2424
  function formatScaledInt(value, scale) {
2401
2425
  const negative = value < 0n;
2402
2426
  const abs = (negative ? -value : value).toString();
@@ -2616,6 +2640,146 @@ var init_tiers = __esm({
2616
2640
  }
2617
2641
  });
2618
2642
 
2643
+ // src/money/where.ts
2644
+ function isMoneyValueObject2(v) {
2645
+ return typeof v === "object" && v !== null && "currency" in v;
2646
+ }
2647
+ function parseOperand(field, raw, desc) {
2648
+ let amount;
2649
+ let currency;
2650
+ if (desc.mode === "fixed") {
2651
+ currency = desc.fixedCurrency;
2652
+ amount = raw;
2653
+ } else if (isMoneyValueObject2(raw)) {
2654
+ currency = String(raw.currency);
2655
+ amount = raw.amount;
2656
+ } else {
2657
+ const sole = desc.soleCurrency();
2658
+ if (sole === void 0) {
2659
+ throw new MoneyUnsupportedError(
2660
+ `where("${field}"): field is multi-currency \u2014 compare against { amount, currency }, not a bare amount`
2661
+ );
2662
+ }
2663
+ currency = sole;
2664
+ amount = raw;
2665
+ }
2666
+ if (typeof amount !== "number" && typeof amount !== "string") {
2667
+ throw new MoneyUnsupportedError(
2668
+ `where("${field}"): operand ${JSON.stringify(raw)} is not a money amount`
2669
+ );
2670
+ }
2671
+ const r = parseToScaledInt(amount, desc.scaleFor(currency), desc.rounding);
2672
+ if (!r.ok) {
2673
+ throw new MoneyUnsupportedError(
2674
+ `where("${field}"): operand ${JSON.stringify(amount)} is not a finite decimal`
2675
+ );
2676
+ }
2677
+ return { scaled: r.value.toString(), currency };
2678
+ }
2679
+ function moneyFieldClause(field, op, value, desc) {
2680
+ switch (op) {
2681
+ case "==":
2682
+ case "!=":
2683
+ case "<":
2684
+ case "<=":
2685
+ case ">":
2686
+ case ">=": {
2687
+ const e = parseOperand(field, value, desc);
2688
+ return withMoney(field, op, value, desc, [e]);
2689
+ }
2690
+ case "between": {
2691
+ if (!Array.isArray(value) || value.length !== 2) {
2692
+ throw new MoneyUnsupportedError(`where("${field}"): 'between' needs a [lo, hi] tuple`);
2693
+ }
2694
+ const lo = parseOperand(field, value[0], desc);
2695
+ const hi = parseOperand(field, value[1], desc);
2696
+ if (lo.currency !== hi.currency) {
2697
+ throw new MoneyUnsupportedError(
2698
+ `where("${field}"): 'between' bounds mix currencies (${lo.currency} vs ${hi.currency})`
2699
+ );
2700
+ }
2701
+ return withMoney(field, op, value, desc, [lo, hi]);
2702
+ }
2703
+ case "in": {
2704
+ if (!Array.isArray(value)) {
2705
+ throw new MoneyUnsupportedError(`where("${field}"): 'in' needs an array of amounts`);
2706
+ }
2707
+ return withMoney(field, op, value, desc, value.map((v) => parseOperand(field, v, desc)));
2708
+ }
2709
+ default:
2710
+ throw new MoneyUnsupportedError(
2711
+ `where("${field}"): operator '${op}' is not supported on a money field`
2712
+ );
2713
+ }
2714
+ }
2715
+ function withMoney(field, op, originalValue, desc, entries) {
2716
+ const money2 = { mode: desc.mode, entries };
2717
+ const value = desc.mode !== "fixed" ? originalValue : entries.length === 1 && op !== "in" && op !== "between" ? entries[0].scaled : entries.map((e) => e.scaled);
2718
+ return { type: "field", field, op, value, money: money2 };
2719
+ }
2720
+ function readStored(actual, operand) {
2721
+ let amount;
2722
+ let currency;
2723
+ if (operand.mode === "fixed") {
2724
+ if (typeof actual !== "string" && typeof actual !== "number") return null;
2725
+ amount = actual;
2726
+ currency = operand.entries[0]?.currency ?? "";
2727
+ } else {
2728
+ if (!isMoneyValueObject2(actual)) return null;
2729
+ if (typeof actual.currency !== "string") return null;
2730
+ amount = actual.amount;
2731
+ currency = actual.currency;
2732
+ }
2733
+ if (typeof amount !== "string" && typeof amount !== "number") return null;
2734
+ try {
2735
+ return { scaled: BigInt(amount).toString(), currency };
2736
+ } catch {
2737
+ return null;
2738
+ }
2739
+ }
2740
+ function evaluateMoneyClause(actual, op, operand) {
2741
+ const stored = readStored(actual, operand);
2742
+ if (stored === null) return op === "!=";
2743
+ const a = BigInt(stored.scaled);
2744
+ if (op === "in") {
2745
+ return operand.entries.some(
2746
+ (e2) => e2.currency === stored.currency && BigInt(e2.scaled) === a
2747
+ );
2748
+ }
2749
+ if (op === "between") {
2750
+ const [lo, hi] = operand.entries;
2751
+ if (!lo || !hi || lo.currency !== stored.currency) return false;
2752
+ return a >= BigInt(lo.scaled) && a <= BigInt(hi.scaled);
2753
+ }
2754
+ const e = operand.entries[0];
2755
+ if (!e) return false;
2756
+ if (e.currency !== stored.currency) return op === "!=";
2757
+ const b = BigInt(e.scaled);
2758
+ switch (op) {
2759
+ case "==":
2760
+ return a === b;
2761
+ case "!=":
2762
+ return a !== b;
2763
+ case "<":
2764
+ return a < b;
2765
+ case "<=":
2766
+ return a <= b;
2767
+ case ">":
2768
+ return a > b;
2769
+ case ">=":
2770
+ return a >= b;
2771
+ default:
2772
+ return false;
2773
+ }
2774
+ }
2775
+ var init_where = __esm({
2776
+ "src/money/where.ts"() {
2777
+ "use strict";
2778
+ init_fixed_point();
2779
+ init_descriptor();
2780
+ }
2781
+ });
2782
+
2619
2783
  // src/query/predicate.ts
2620
2784
  function readPath(record, path) {
2621
2785
  if (record === null || record === void 0) return void 0;
@@ -2633,6 +2797,7 @@ function readPath(record, path) {
2633
2797
  function evaluateFieldClause(record, clause) {
2634
2798
  const actual = readPath(record, clause.field);
2635
2799
  const { op, value } = clause;
2800
+ if (clause.money) return evaluateMoneyClause(actual, op, clause.money);
2636
2801
  switch (op) {
2637
2802
  case "==":
2638
2803
  return actual === value;
@@ -2673,14 +2838,14 @@ function isComparable(a, b) {
2673
2838
  if (a instanceof Date && b instanceof Date) return true;
2674
2839
  return false;
2675
2840
  }
2676
- function evaluateClause(record, clause) {
2841
+ function evaluateClause(record, clause, fnRecord) {
2677
2842
  switch (clause.type) {
2678
2843
  case "field":
2679
2844
  return evaluateFieldClause(record, clause);
2680
2845
  case "filter":
2681
- return clause.fn(record);
2846
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record);
2682
2847
  case "wherePredicate":
2683
- return clause.fn(record, clause.ctx);
2848
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record, clause.ctx);
2684
2849
  case "crossJoin":
2685
2850
  throw new Error(
2686
2851
  `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.`
@@ -2688,31 +2853,48 @@ function evaluateClause(record, clause) {
2688
2853
  case "group":
2689
2854
  if (clause.op === "and") {
2690
2855
  for (const child of clause.clauses) {
2691
- if (!evaluateClause(record, child)) return false;
2856
+ if (!evaluateClause(record, child, fnRecord)) return false;
2692
2857
  }
2693
2858
  return true;
2694
2859
  } else {
2695
2860
  for (const child of clause.clauses) {
2696
- if (evaluateClause(record, child)) return true;
2861
+ if (evaluateClause(record, child, fnRecord)) return true;
2697
2862
  }
2698
2863
  return false;
2699
2864
  }
2700
2865
  }
2701
2866
  }
2867
+ function hasFnClause(clauses) {
2868
+ for (const c of clauses) {
2869
+ if (c.type === "filter" || c.type === "wherePredicate") return true;
2870
+ if (c.type === "group" && hasFnClause(c.clauses)) return true;
2871
+ }
2872
+ return false;
2873
+ }
2702
2874
  var init_predicate = __esm({
2703
2875
  "src/query/predicate.ts"() {
2704
2876
  "use strict";
2877
+ init_where();
2705
2878
  }
2706
2879
  });
2707
2880
 
2708
2881
  // src/money/money-reducer.ts
2709
- function toScaledInt(v) {
2710
- if (typeof v === "string" || typeof v === "number" || typeof v === "bigint") {
2711
- try {
2712
- return BigInt(v);
2713
- } catch {
2714
- return null;
2882
+ function toScaledIntFromAny(v, scale) {
2883
+ if (typeof v === "bigint") return v;
2884
+ if (typeof v === "number") {
2885
+ const r = parseToScaledInt(v, scale);
2886
+ return r.ok ? r.value : null;
2887
+ }
2888
+ if (typeof v === "string") {
2889
+ if (!v.includes(".")) {
2890
+ try {
2891
+ return BigInt(v);
2892
+ } catch {
2893
+ return null;
2894
+ }
2715
2895
  }
2896
+ const r = parseToScaledInt(v, scale);
2897
+ return r.ok ? r.value : null;
2716
2898
  }
2717
2899
  return null;
2718
2900
  }
@@ -2720,13 +2902,15 @@ function readMoney(record, field, desc) {
2720
2902
  const raw = readPath(record, field);
2721
2903
  if (raw === null || raw === void 0) return null;
2722
2904
  if (desc.mode === "fixed") {
2723
- const value2 = toScaledInt(raw);
2724
- return value2 === null ? null : { currency: desc.fixedCurrency, value: value2 };
2905
+ const cur = desc.fixedCurrency;
2906
+ const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
2907
+ return value2 === null ? null : { currency: cur, value: value2 };
2725
2908
  }
2726
2909
  if (typeof raw !== "object") return null;
2727
2910
  const o = raw;
2728
2911
  if (typeof o.currency !== "string") return null;
2729
- const value = toScaledInt(o.amount);
2912
+ const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
2913
+ const value = toScaledIntFromAny(o.amount, scale);
2730
2914
  return value === null ? null : { currency: o.currency, value };
2731
2915
  }
2732
2916
  function targetScaleFor(desc, currency) {
@@ -3044,11 +3228,14 @@ function warnCardinalityApproaching(fields, observed) {
3044
3228
  `[noy-db] .groupBy(${label}) produced ${observed} distinct groups, ${Math.round(observed / GROUPBY_MAX_CARDINALITY * 100)}% of the ${GROUPBY_MAX_CARDINALITY}-group ceiling. Narrow the query with .where() before grouping, or switch to a lower-cardinality field.`
3045
3229
  );
3046
3230
  }
3047
- function groupAndReduce(records, fieldOrFields, spec) {
3231
+ function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
3048
3232
  const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
3049
3233
  if (fields.length === 0) {
3050
3234
  throw new Error(".groupBy() requires at least one field");
3051
3235
  }
3236
+ if (moneyFields) {
3237
+ spec = wrapMoneyReducers(spec, moneyFields);
3238
+ }
3052
3239
  const buckets = /* @__PURE__ */ new Map();
3053
3240
  const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
3054
3241
  for (const record of records) {
@@ -3491,10 +3678,14 @@ function summarizeQueryPlan(query) {
3491
3678
  });
3492
3679
  }
3493
3680
  function summarizeUnionPlan(spec) {
3494
- const arms = (spec.unionSources ?? []).map((s) => s.collection).join(",");
3681
+ const arms = (spec.unionSources ?? []).map((s) => {
3682
+ const joins = s.join?.length ? `[${s.join.map((j) => `${j.field}\u2192${j.as}`).join(";")}]` : "";
3683
+ return `${s.collection}${joins}`;
3684
+ }).join(",");
3495
3685
  const groupBy = Array.isArray(spec.groupBy) ? [...spec.groupBy].sort().join(",") : typeof spec.groupBy === "string" ? spec.groupBy : "";
3496
3686
  const aggKeys = spec.aggregate ? Object.keys(spec.aggregate).sort().join(",") : "";
3497
- return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})`;
3687
+ const moneyKeys = spec.moneyFields ? Object.keys(spec.moneyFields).sort().join(",") : "";
3688
+ return `union(${arms})|groupBy(${groupBy})|aggregate(${aggKeys})|money(${moneyKeys})`;
3498
3689
  }
3499
3690
  var init_dependency_analyzer = __esm({
3500
3691
  "src/materialized-views/dependency-analyzer.ts"() {
@@ -3621,6 +3812,7 @@ var init_registry = __esm({
3621
3812
  let isQuery = false;
3622
3813
  if (spec.unionSources) {
3623
3814
  dependencies = new Set(spec.unionSources.map((s) => s.collection));
3815
+ if (spec.sources) for (const s of spec.sources) dependencies.add(s);
3624
3816
  queryPlanSummary = summarizeUnionPlan(spec);
3625
3817
  } else {
3626
3818
  const q = spec.query(dbForQuery);
@@ -3775,7 +3967,13 @@ async function materializeUnionResult(spec, db) {
3775
3967
  const unified = [];
3776
3968
  for (const arm of spec.unionSources) {
3777
3969
  const coll = db.collection(arm.collection);
3778
- const sourceRows = coll.query().toArray();
3970
+ let q = coll.query();
3971
+ if (arm.join?.length) {
3972
+ for (const leg of arm.join) {
3973
+ q = q.join(leg.field, { as: leg.as, maxRows: leg.maxRows, strategy: leg.strategy });
3974
+ }
3975
+ }
3976
+ const sourceRows = q.toArray();
3779
3977
  for (const r of sourceRows) {
3780
3978
  const mapped = arm.map(r);
3781
3979
  if (mapped == null) continue;
@@ -3792,7 +3990,7 @@ async function materializeUnionResult(spec, db) {
3792
3990
  }
3793
3991
  return [...seen.values()];
3794
3992
  }
3795
- return groupAndReduce(unified, groupFields, spec.aggregate);
3993
+ return groupAndReduce(unified, groupFields, spec.aggregate, spec.moneyFields);
3796
3994
  }
3797
3995
  async function listOutputIds(outputColl) {
3798
3996
  const cAny = outputColl;
@@ -4347,11 +4545,15 @@ var init_read_only_facade = __esm({
4347
4545
  });
4348
4546
 
4349
4547
  // src/derivations/strategy-hash.ts
4350
- async function computeStrategyHash(source, outputKeys, derive) {
4548
+ async function computeStrategyHash(source, outputKeys, derive, sources) {
4351
4549
  const canonical2 = JSON.stringify({
4352
4550
  source,
4353
4551
  outputs: [...outputKeys].sort(),
4354
- derive: derive.toString()
4552
+ derive: derive.toString(),
4553
+ // Declared sibling sources (#344) — adding/removing a trigger
4554
+ // collection invalidates cached derived records. Omitted when empty
4555
+ // so strategies without siblings keep their existing hash.
4556
+ ...sources?.length ? { sources: [...sources].sort() } : {}
4355
4557
  });
4356
4558
  const bytes = new TextEncoder().encode(canonical2);
4357
4559
  const digest = await crypto.subtle.digest("SHA-256", bytes);
@@ -4380,11 +4582,16 @@ var init_registry3 = __esm({
4380
4582
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
4381
4583
  async register(spec) {
4382
4584
  const outputKeys = Object.keys(spec.outputs);
4383
- const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive);
4585
+ const strategyHash = await computeStrategyHash(spec.source, outputKeys, spec.derive, spec.sources);
4384
4586
  const reg = { spec, strategyHash };
4385
4587
  const fromSource = this._bySource.get(spec.source);
4386
4588
  if (fromSource) fromSource.push(reg);
4387
4589
  else this._bySource.set(spec.source, [reg]);
4590
+ for (const extra of spec.sources ?? []) {
4591
+ const fromExtra = this._bySource.get(extra);
4592
+ if (fromExtra) fromExtra.push(reg);
4593
+ else this._bySource.set(extra, [reg]);
4594
+ }
4388
4595
  for (const key of outputKeys) {
4389
4596
  const output = spec.outputs[key];
4390
4597
  if (!output) continue;
@@ -5577,9 +5784,11 @@ __export(src_exports, {
5577
5784
  WeakPassphraseError: () => WeakPassphraseError,
5578
5785
  activeSessionCount: () => activeSessionCount,
5579
5786
  additiveOnly: () => additiveOnly,
5787
+ allocate: () => allocate,
5580
5788
  applyI18nLocale: () => applyI18nLocale,
5581
5789
  applyJoins: () => applyJoins,
5582
5790
  applyPatch: () => applyPatch,
5791
+ asMoney: () => asMoney,
5583
5792
  assertStrongPassphrase: () => assertStrongPassphrase,
5584
5793
  assertTierAccess: () => assertTierAccess,
5585
5794
  avg: () => avg,
@@ -5656,6 +5865,7 @@ __export(src_exports, {
5656
5865
  isI18nTextDescriptor: () => isI18nTextDescriptor,
5657
5866
  isMagicLinkGrantExpired: () => isMagicLinkGrantExpired,
5658
5867
  isMoneyDescriptor: () => isMoneyDescriptor,
5868
+ isMoneyString: () => isMoneyString,
5659
5869
  isPreCompressed: () => isPreCompressed,
5660
5870
  isPublicEnvelope: () => isPublicEnvelope,
5661
5871
  isSessionAlive: () => isSessionAlive,
@@ -5688,6 +5898,8 @@ __export(src_exports, {
5688
5898
  mintShamirRecoveryEntry: () => mintShamirRecoveryEntry,
5689
5899
  mintWrappedDeksBlob: () => mintWrappedDeksBlob,
5690
5900
  money: () => money,
5901
+ moneyNumber: () => moneyNumber,
5902
+ mulRate: () => mulRate,
5691
5903
  paddedIndex: () => paddedIndex,
5692
5904
  parseBytes: () => parseBytes,
5693
5905
  parseIndex: () => parseIndex,
@@ -5714,6 +5926,7 @@ __export(src_exports, {
5714
5926
  resolveI18nText: () => resolveI18nText,
5715
5927
  resolvePolicy: () => resolvePolicy,
5716
5928
  resolvePublicEnvelopeSchema: () => resolveSchema,
5929
+ resolveSequenceKey: () => resolveSequenceKey,
5717
5930
  resolveSession: () => resolveSession,
5718
5931
  revokeAllSessions: () => revokeAllSessions,
5719
5932
  revokeDelegation: () => revokeDelegation,
@@ -7812,6 +8025,21 @@ init_crypto();
7812
8025
  init_errors();
7813
8026
  var SEQUENCE_COLLECTION = "_sequences";
7814
8027
  var MAX_NEXT_ATTEMPTS = 16;
8028
+ function resolveSequenceKey(series, opts) {
8029
+ const partition = opts?.partition;
8030
+ if (!partition || partition.length === 0) return series;
8031
+ const parts = partition.map((p) => {
8032
+ if (typeof p === "number" && !Number.isFinite(p)) {
8033
+ throw new ValidationError(`sequence partition component must be a finite number, got ${p}`);
8034
+ }
8035
+ const s = String(p);
8036
+ if (s === "") {
8037
+ throw new ValidationError("sequence partition component must not be empty");
8038
+ }
8039
+ return encodeURIComponent(s);
8040
+ });
8041
+ return `${series}\0${parts.join("/")}`;
8042
+ }
7815
8043
  async function sleepBackoff(attempt) {
7816
8044
  const ceil = Math.min(2 ** attempt, 32);
7817
8045
  const ms = Math.floor(Math.random() * ceil);
@@ -7842,7 +8070,8 @@ var SequenceStore = class {
7842
8070
  handle(name) {
7843
8071
  return {
7844
8072
  next: () => this.next(name),
7845
- peek: () => this.peek(name)
8073
+ peek: () => this.peek(name),
8074
+ seedTo: (n) => this.seedTo(name, n)
7846
8075
  };
7847
8076
  }
7848
8077
  assertOnline() {
@@ -7895,6 +8124,30 @@ var SequenceStore = class {
7895
8124
  void lastConflict;
7896
8125
  throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
7897
8126
  }
8127
+ async seedTo(name, n) {
8128
+ this.assertOnline();
8129
+ if (n <= 0) return;
8130
+ let lastConflict;
8131
+ for (let attempt = 0; attempt < MAX_NEXT_ATTEMPTS; attempt++) {
8132
+ const { env, value } = await this.read(name);
8133
+ if (value >= n) return;
8134
+ const expectedVersion = env?._v ?? 0;
8135
+ const envelope = await this.encryptState({ value: n }, expectedVersion + 1);
8136
+ try {
8137
+ await this.adapter.put(this.vault, SEQUENCE_COLLECTION, name, envelope, expectedVersion);
8138
+ return;
8139
+ } catch (err) {
8140
+ if (err instanceof ConflictError) {
8141
+ lastConflict = err;
8142
+ if (attempt < MAX_NEXT_ATTEMPTS - 1) await sleepBackoff(attempt);
8143
+ continue;
8144
+ }
8145
+ throw err;
8146
+ }
8147
+ }
8148
+ void lastConflict;
8149
+ throw new SequenceContentionError(name, MAX_NEXT_ATTEMPTS);
8150
+ }
7898
8151
  };
7899
8152
 
7900
8153
  // src/numbering/descriptor.ts
@@ -11579,6 +11832,111 @@ function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
11579
11832
  // src/money/normalize.ts
11580
11833
  init_fixed_point();
11581
11834
  init_descriptor();
11835
+
11836
+ // src/money/paths.ts
11837
+ init_errors();
11838
+ var SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
11839
+ var parseCache = /* @__PURE__ */ new Map();
11840
+ function parseMoneyPath(path) {
11841
+ const cached = parseCache.get(path);
11842
+ if (cached) return cached;
11843
+ if (typeof path !== "string" || path.length === 0) {
11844
+ throw new ValidationError("moneyFields: path must be a non-empty string");
11845
+ }
11846
+ const segments = [];
11847
+ for (const part of path.split(".")) {
11848
+ const m = SEGMENT_RE.exec(part);
11849
+ if (!m) {
11850
+ throw new ValidationError(
11851
+ `moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
11852
+ );
11853
+ }
11854
+ const array = m[2] === "[]";
11855
+ segments.push(
11856
+ m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
11857
+ );
11858
+ }
11859
+ parseCache.set(path, segments);
11860
+ return segments;
11861
+ }
11862
+ function isSimpleMoneyPath(path) {
11863
+ return !path.includes(".") && !path.includes("[") && !path.includes("*");
11864
+ }
11865
+ function validateMoneyFieldPaths(moneyFields) {
11866
+ for (const path of Object.keys(moneyFields)) parseMoneyPath(path);
11867
+ }
11868
+ function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
11869
+ if (node === null || node === void 0) return node;
11870
+ const seg = segments[index];
11871
+ const last = index === segments.length - 1;
11872
+ if (seg.kind === "key") {
11873
+ if (typeof node !== "object" || Array.isArray(node)) {
11874
+ if (lenient) return node;
11875
+ throw new ValidationError(
11876
+ `moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
11877
+ );
11878
+ }
11879
+ const obj2 = node;
11880
+ if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
11881
+ if (seg.array) {
11882
+ const arr = obj2[seg.key];
11883
+ if (!Array.isArray(arr)) {
11884
+ if (lenient) return node;
11885
+ throw new ValidationError(
11886
+ `moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
11887
+ );
11888
+ }
11889
+ const cloned = [...arr];
11890
+ if (last) {
11891
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
11892
+ } else {
11893
+ for (let i = 0; i < cloned.length; i++) {
11894
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
11895
+ }
11896
+ }
11897
+ return { ...obj2, [seg.key]: cloned };
11898
+ }
11899
+ const clone3 = { ...obj2 };
11900
+ if (last) {
11901
+ visit(clone3, seg.key);
11902
+ } else {
11903
+ clone3[seg.key] = transformAtMoneyPath(clone3[seg.key], path, segments, index + 1, visit, lenient);
11904
+ }
11905
+ return clone3;
11906
+ }
11907
+ if (seg.array) {
11908
+ if (!Array.isArray(node)) {
11909
+ if (lenient) return node;
11910
+ throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
11911
+ }
11912
+ const cloned = [...node];
11913
+ if (last) {
11914
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
11915
+ } else {
11916
+ for (let i = 0; i < cloned.length; i++) {
11917
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
11918
+ }
11919
+ }
11920
+ return cloned;
11921
+ }
11922
+ if (typeof node !== "object" || Array.isArray(node)) {
11923
+ if (lenient) return node;
11924
+ throw new ValidationError(
11925
+ `moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
11926
+ );
11927
+ }
11928
+ const obj = node;
11929
+ const clone2 = { ...obj };
11930
+ for (const key of Object.keys(obj)) {
11931
+ const v = clone2[key];
11932
+ if (v === null || v === void 0) continue;
11933
+ if (last) visit(clone2, key);
11934
+ else clone2[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
11935
+ }
11936
+ return clone2;
11937
+ }
11938
+
11939
+ // src/money/normalize.ts
11582
11940
  function isMoneyValueObject(v) {
11583
11941
  return typeof v === "object" && v !== null && "currency" in v;
11584
11942
  }
@@ -11590,33 +11948,68 @@ function quantizeAmount(field, input, scale, rounding) {
11590
11948
  }
11591
11949
  return r.value.toString();
11592
11950
  }
11951
+ function canonicalizeStoredMoney(record, moneyFields) {
11952
+ if (record === null || record === void 0) return record;
11953
+ if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
11954
+ return decodeMoneyFields(record, moneyFields, "raw");
11955
+ }
11956
+ function canonicalizeIncomingMoney(record, moneyFields) {
11957
+ if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
11958
+ try {
11959
+ return decodeMoneyFields(
11960
+ quantizeMoneyFields(record, moneyFields),
11961
+ moneyFields,
11962
+ "raw"
11963
+ );
11964
+ } catch {
11965
+ return record;
11966
+ }
11967
+ }
11968
+ function quantizeValue(field, raw, desc) {
11969
+ if (desc.mode === "fixed") {
11970
+ const currency2 = desc.fixedCurrency;
11971
+ return quantizeAmount(field, raw, desc.scaleFor(currency2), desc.rounding);
11972
+ }
11973
+ let amount;
11974
+ let currency;
11975
+ if (isMoneyValueObject(raw)) {
11976
+ currency = String(raw.currency);
11977
+ amount = raw.amount;
11978
+ } else {
11979
+ const sole = desc.soleCurrency();
11980
+ if (sole === void 0) {
11981
+ throw new TypeError(
11982
+ `money: field "${field}" is multi-currency \u2014 write { amount, currency }, not a bare amount`
11983
+ );
11984
+ }
11985
+ currency = sole;
11986
+ amount = raw;
11987
+ }
11988
+ const scale = desc.scaleFor(currency);
11989
+ return { amount: quantizeAmount(field, amount, scale, desc.rounding), currency };
11990
+ }
11593
11991
  function quantizeMoneyFields(record, moneyFields) {
11594
- const out = { ...record };
11595
- for (const [field, desc] of Object.entries(moneyFields)) {
11596
- const raw = out[field];
11597
- if (raw === null || raw === void 0) continue;
11598
- if (desc.mode === "fixed") {
11599
- const currency2 = desc.fixedCurrency;
11600
- out[field] = quantizeAmount(field, raw, desc.scaleFor(currency2), desc.rounding);
11992
+ let out = { ...record };
11993
+ for (const [path, desc] of Object.entries(moneyFields)) {
11994
+ if (isSimpleMoneyPath(path)) {
11995
+ const raw = out[path];
11996
+ if (raw === null || raw === void 0) continue;
11997
+ out[path] = quantizeValue(path, raw, desc);
11601
11998
  continue;
11602
11999
  }
11603
- let amount;
11604
- let currency;
11605
- if (isMoneyValueObject(raw)) {
11606
- currency = String(raw.currency);
11607
- amount = raw.amount;
11608
- } else {
11609
- const sole = desc.soleCurrency();
11610
- if (sole === void 0) {
11611
- throw new TypeError(
11612
- `money: field "${field}" is multi-currency \u2014 write { amount, currency }, not a bare amount`
11613
- );
11614
- }
11615
- currency = sole;
11616
- amount = raw;
11617
- }
11618
- const scale = desc.scaleFor(currency);
11619
- out[field] = { amount: quantizeAmount(field, amount, scale, desc.rounding), currency };
12000
+ out = transformAtMoneyPath(
12001
+ out,
12002
+ path,
12003
+ parseMoneyPath(path),
12004
+ 0,
12005
+ (container, key) => {
12006
+ const raw = container[key];
12007
+ if (raw === null || raw === void 0) return;
12008
+ container[key] = quantizeValue(path, raw, desc);
12009
+ },
12010
+ /* lenient */
12011
+ false
12012
+ );
11620
12013
  }
11621
12014
  return out;
11622
12015
  }
@@ -11629,33 +12022,70 @@ function formatCurrency(decimal, currency, scale, locale) {
11629
12022
  });
11630
12023
  return fmt.format(decimal);
11631
12024
  }
12025
+ function decodeValue(stored, desc) {
12026
+ let currency;
12027
+ let scaledIntString;
12028
+ if (desc.mode === "fixed") {
12029
+ if (typeof stored !== "string" && typeof stored !== "number") return null;
12030
+ currency = desc.fixedCurrency;
12031
+ scaledIntString = String(stored);
12032
+ } else {
12033
+ if (!isMoneyValueObject(stored)) return null;
12034
+ const amount = stored.amount;
12035
+ if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
12036
+ currency = stored.currency;
12037
+ scaledIntString = String(amount);
12038
+ }
12039
+ const scale = desc.scaleFor(currency);
12040
+ let decimal;
12041
+ try {
12042
+ decimal = formatScaledInt(BigInt(scaledIntString), scale);
12043
+ } catch {
12044
+ return null;
12045
+ }
12046
+ return {
12047
+ decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
12048
+ decimal,
12049
+ currency,
12050
+ scale
12051
+ };
12052
+ }
11632
12053
  function decodeMoneyFields(record, moneyFields, locale) {
11633
- const out = { ...record };
12054
+ let out = { ...record };
11634
12055
  const format = locale !== "raw";
11635
12056
  const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
11636
- for (const [field, desc] of Object.entries(moneyFields)) {
11637
- const stored = out[field];
11638
- if (stored === null || stored === void 0) continue;
11639
- let currency;
11640
- let scaledIntString;
11641
- if (desc.mode === "fixed") {
11642
- if (typeof stored !== "string" && typeof stored !== "number") continue;
11643
- currency = desc.fixedCurrency;
11644
- scaledIntString = String(stored);
11645
- } else {
11646
- if (!isMoneyValueObject(stored)) continue;
11647
- const amount = stored.amount;
11648
- if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") continue;
11649
- currency = stored.currency;
11650
- scaledIntString = String(amount);
11651
- }
11652
- const scale = desc.scaleFor(currency);
11653
- const decimal = formatScaledInt(BigInt(scaledIntString), scale);
11654
- out[field] = desc.mode === "fixed" ? decimal : { amount: decimal, currency };
11655
- if (format) {
11656
- out[`${field}Formatted`] = formatCurrency(decimal, currency, scale, fmtLocale);
11657
- out[`${field}Number`] = Number(decimal);
12057
+ for (const [path, desc] of Object.entries(moneyFields)) {
12058
+ if (isSimpleMoneyPath(path)) {
12059
+ const stored = out[path];
12060
+ if (stored === null || stored === void 0) continue;
12061
+ const r = decodeValue(stored, desc);
12062
+ if (r === null) continue;
12063
+ out[path] = r.decoded;
12064
+ if (format) {
12065
+ out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
12066
+ out[`${path}Number`] = Number(r.decimal);
12067
+ }
12068
+ continue;
11658
12069
  }
12070
+ out = transformAtMoneyPath(
12071
+ out,
12072
+ path,
12073
+ parseMoneyPath(path),
12074
+ 0,
12075
+ (container, key) => {
12076
+ const stored = container[key];
12077
+ if (stored === null || stored === void 0) return;
12078
+ const r = decodeValue(stored, desc);
12079
+ if (r === null) return;
12080
+ container[key] = r.decoded;
12081
+ if (format && typeof key === "string" && !Array.isArray(container)) {
12082
+ container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
12083
+ container[`${key}Number`] = Number(r.decimal);
12084
+ }
12085
+ },
12086
+ /* lenient */
12087
+ true
12088
+ );
11659
12089
  }
11660
12090
  return out;
11661
12091
  }
@@ -12008,6 +12438,7 @@ var NO_AGGREGATE = {
12008
12438
 
12009
12439
  // src/query/builder.ts
12010
12440
  init_money_reducer();
12441
+ init_where();
12011
12442
  var EMPTY_PLAN = {
12012
12443
  clauses: [],
12013
12444
  orderBy: [],
@@ -12099,9 +12530,18 @@ var Query = class _Query {
12099
12530
  this.predicates
12100
12531
  );
12101
12532
  }
12102
- /** Add a field comparison. Multiple where() calls are AND-combined. */
12533
+ /**
12534
+ * Add a field comparison. Multiple where() calls are AND-combined.
12535
+ *
12536
+ * A declared money field compares in MAJOR units (#336): the operand
12537
+ * (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
12538
+ * quantized into stored scaled-int space at build time and evaluated
12539
+ * BigInt-exact per record. A malformed operand or a string operator
12540
+ * (`contains`/`startsWith`) throws here, at the call site.
12541
+ */
12103
12542
  where(field, op, value) {
12104
- const clause = { type: "field", field, op, value };
12543
+ const desc = this.source.moneyFields?.[field];
12544
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
12105
12545
  return new _Query(
12106
12546
  this.source,
12107
12547
  { ...this.plan, clauses: [...this.plan.clauses, clause] },
@@ -12427,7 +12867,7 @@ var Query = class _Query {
12427
12867
  }
12428
12868
  const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
12429
12869
  if (remainingClauses.length === 0) return candidates.length;
12430
- return filterRecords(candidates, remainingClauses).length;
12870
+ return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
12431
12871
  }
12432
12872
  /**
12433
12873
  * Reduce the matching records through a named set of reducers.
@@ -12484,7 +12924,7 @@ var Query = class _Query {
12484
12924
  return executeClausePipeline(source, clauses, joinCtx);
12485
12925
  }
12486
12926
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
12487
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
12927
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
12488
12928
  };
12489
12929
  const upstreams = [];
12490
12930
  if (source.subscribe) {
@@ -12507,7 +12947,7 @@ var Query = class _Query {
12507
12947
  return executeClausePipeline(source, clauses, joinCtx);
12508
12948
  }
12509
12949
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
12510
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
12950
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
12511
12951
  };
12512
12952
  const upstreams = [];
12513
12953
  if (source.subscribe) {
@@ -12659,7 +13099,7 @@ function executePlanWithSource(source, plan, joinContext) {
12659
13099
  result = executeClausePipeline(source, plan.clauses, joinContext);
12660
13100
  } else {
12661
13101
  const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
12662
- result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses);
13102
+ result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
12663
13103
  }
12664
13104
  if (plan.orderBy.length > 0) {
12665
13105
  result = sortRecords(result, plan.orderBy);
@@ -12682,6 +13122,7 @@ function candidateRecords(source, clauses) {
12682
13122
  const clause = clauses[i];
12683
13123
  if (clause.type !== "field") continue;
12684
13124
  if (!indexes.has(clause.field)) continue;
13125
+ if (clause.money?.mode === "multi") continue;
12685
13126
  let ids = null;
12686
13127
  if (clause.op === "==") {
12687
13128
  ids = indexes.lookupEqual(clause.field, clause.value);
@@ -12727,13 +13168,20 @@ function executePlan(records, plan) {
12727
13168
  }
12728
13169
  return result;
12729
13170
  }
12730
- function filterRecords(records, clauses) {
13171
+ function fnViewDecoder(source) {
13172
+ const mf = source.moneyFields;
13173
+ if (!mf || Object.keys(mf).length === 0) return void 0;
13174
+ return (r) => decodeMoneyFields(r, mf, "raw");
13175
+ }
13176
+ function filterRecords(records, clauses, decodeForFns) {
12731
13177
  if (clauses.length === 0) return [...records];
13178
+ const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
12732
13179
  const out = [];
12733
13180
  for (const r of records) {
13181
+ const fnView = needsFnView ? decodeForFns(r) : void 0;
12734
13182
  let matches = true;
12735
13183
  for (const clause of clauses) {
12736
- if (!evaluateClause(r, clause)) {
13184
+ if (!evaluateClause(r, clause, fnView)) {
12737
13185
  matches = false;
12738
13186
  break;
12739
13187
  }
@@ -12745,10 +13193,11 @@ function filterRecords(records, clauses) {
12745
13193
  function executeClausePipeline(source, clauses, joinContext) {
12746
13194
  let rel = [...source.snapshot()];
12747
13195
  let filterBatch = [];
13196
+ const decodeForFns = fnViewDecoder(source);
12748
13197
  for (const clause of clauses) {
12749
13198
  if (clause.type === "crossJoin") {
12750
13199
  if (filterBatch.length > 0) {
12751
- rel = filterRecords(rel, filterBatch);
13200
+ rel = filterRecords(rel, filterBatch, decodeForFns);
12752
13201
  filterBatch = [];
12753
13202
  }
12754
13203
  const rightSource = joinContext.resolveSource(clause.target);
@@ -12761,7 +13210,7 @@ function executeClausePipeline(source, clauses, joinContext) {
12761
13210
  }
12762
13211
  }
12763
13212
  if (filterBatch.length > 0) {
12764
- rel = filterRecords(rel, filterBatch);
13213
+ rel = filterRecords(rel, filterBatch, decodeForFns);
12765
13214
  }
12766
13215
  return rel;
12767
13216
  }
@@ -13179,6 +13628,7 @@ init_groupby();
13179
13628
  // src/query/scan-builder.ts
13180
13629
  init_predicate();
13181
13630
  init_errors();
13631
+ init_where();
13182
13632
  var DEFAULT_SCAN_PAGE_SIZE = 100;
13183
13633
  var ScanBuilder = class _ScanBuilder {
13184
13634
  pageProvider;
@@ -13242,7 +13692,8 @@ var ScanBuilder = class _ScanBuilder {
13242
13692
  * evaluates clauses per record in O(1) per clause.
13243
13693
  */
13244
13694
  where(field, op, value) {
13245
- const clause = { type: "field", field, op, value };
13695
+ const desc = this.moneyFields?.[field];
13696
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
13246
13697
  return new _ScanBuilder(
13247
13698
  this.pageProvider,
13248
13699
  this.pageSize,
@@ -13588,8 +14039,9 @@ var ScanBuilder = class _ScanBuilder {
13588
14039
  */
13589
14040
  recordMatches(record) {
13590
14041
  if (this.clauses.length === 0) return true;
14042
+ const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
13591
14043
  for (const clause of this.clauses) {
13592
- if (!evaluateClause(record, clause)) return false;
14044
+ if (!evaluateClause(record, clause, fnView)) return false;
13593
14045
  }
13594
14046
  return true;
13595
14047
  }
@@ -14222,7 +14674,7 @@ var TxCollection = class {
14222
14674
  this._ctx._ops.push(op);
14223
14675
  }
14224
14676
  };
14225
- async function runTransaction(db, fn, options) {
14677
+ async function runTransaction(db, fn, options, txInvariants) {
14226
14678
  if (options?.amendment) {
14227
14679
  if (typeof options.reason !== "string" || options.reason.trim().length === 0) {
14228
14680
  throw new ValidationError(
@@ -14246,12 +14698,19 @@ async function runTransaction(db, fn, options) {
14246
14698
  }
14247
14699
  const priorEnvelopes = /* @__PURE__ */ new Map();
14248
14700
  const store = db._store;
14701
+ const invariants = txInvariants ?? [];
14702
+ const watchedScopes = new Set(invariants.map((i) => i.scope));
14703
+ const plainBefore = /* @__PURE__ */ new Map();
14249
14704
  for (const op of ctx._ops) {
14250
14705
  const key = keyOf(op);
14251
14706
  if (!priorEnvelopes.has(key)) {
14252
14707
  const env = await store.get(op.vaultName, op.collectionName, op.id);
14253
14708
  priorEnvelopes.set(key, env);
14254
14709
  }
14710
+ if (watchedScopes.has(op.collectionName) && !plainBefore.has(key)) {
14711
+ const prior = await db.vault(op.vaultName).collection(op.collectionName).get(op.id);
14712
+ plainBefore.set(key, prior ?? null);
14713
+ }
14255
14714
  if (op.expectedVersion !== void 0) {
14256
14715
  const env = priorEnvelopes.get(key) ?? null;
14257
14716
  const actual = env?._v ?? 0;
@@ -14349,6 +14808,58 @@ async function runTransaction(db, fn, options) {
14349
14808
  );
14350
14809
  }
14351
14810
  }
14811
+ if (invariants.length > 0) {
14812
+ const lastOp = /* @__PURE__ */ new Map();
14813
+ const order = [];
14814
+ for (const op of ctx._ops) {
14815
+ const key = keyOf(op);
14816
+ if (!lastOp.has(key)) order.push(key);
14817
+ lastOp.set(key, op);
14818
+ }
14819
+ const changesByScope = /* @__PURE__ */ new Map();
14820
+ const scopeVault = /* @__PURE__ */ new Map();
14821
+ for (const key of order) {
14822
+ const op = lastOp.get(key);
14823
+ if (!watchedScopes.has(op.collectionName)) continue;
14824
+ const before = plainBefore.get(key) ?? null;
14825
+ const after = op.type === "delete" ? null : op.record ?? null;
14826
+ const change = { before, after };
14827
+ const arr = changesByScope.get(op.collectionName);
14828
+ if (arr) arr.push(change);
14829
+ else changesByScope.set(op.collectionName, [change]);
14830
+ scopeVault.set(op.collectionName, op.vaultName);
14831
+ }
14832
+ try {
14833
+ for (const inv of invariants) {
14834
+ const changes = changesByScope.get(inv.scope);
14835
+ if (changes === void 0 || changes.length === 0) continue;
14836
+ const vaultName = scopeVault.get(inv.scope);
14837
+ const v = db.vault(vaultName);
14838
+ const facade = v._getReadOnlyFacade() ?? {
14839
+ collection(name) {
14840
+ const c = v.collection(name);
14841
+ return {
14842
+ get: (id) => c.get(id),
14843
+ list: () => c.list(),
14844
+ query: () => c.query()
14845
+ };
14846
+ }
14847
+ };
14848
+ const ctxForInv = {
14849
+ existing: null,
14850
+ vault: facade,
14851
+ userId: v.userId,
14852
+ role: v.role
14853
+ };
14854
+ await inv.check(changes, ctxForInv);
14855
+ }
14856
+ } catch (err) {
14857
+ await revertExecuted(ctx._executed, store, db);
14858
+ throw err instanceof InvariantError ? err : new InvariantError(
14859
+ err instanceof Error ? err.message : `invariant violated: ${String(err)}`
14860
+ );
14861
+ }
14862
+ }
14352
14863
  return bodyResult;
14353
14864
  }
14354
14865
  async function revertExecuted(executed, store, db) {
@@ -14753,6 +15264,7 @@ var Collection = class {
14753
15264
  this.joinResolver = opts.joinResolver;
14754
15265
  this.i18nFields = opts.i18nFields;
14755
15266
  this.dictKeyFields = opts.dictKeyFields;
15267
+ if (opts.moneyFields) validateMoneyFieldPaths(opts.moneyFields);
14756
15268
  this.moneyFields = opts.moneyFields;
14757
15269
  this.computed = opts.computed;
14758
15270
  this.dictLabelResolver = opts.dictLabelResolver;
@@ -14886,7 +15398,9 @@ var Collection = class {
14886
15398
  * declaration; this reconciles that ordering. First-wins. Not public.
14887
15399
  */
14888
15400
  _applyMoneyFields(moneyFields) {
14889
- if (this.moneyFields === void 0) this.moneyFields = moneyFields;
15401
+ if (this.moneyFields !== void 0) return;
15402
+ validateMoneyFieldPaths(moneyFields);
15403
+ this.moneyFields = moneyFields;
14890
15404
  }
14891
15405
  /** @internal — attach computed fields post-construction. See {@link _applyMoneyFields}. */
14892
15406
  _applyComputed(computed) {
@@ -15053,6 +15567,7 @@ var Collection = class {
15053
15567
  if (!hasWritePermission(this.keyring, this.name)) {
15054
15568
  throw new ReadOnlyError();
15055
15569
  }
15570
+ record = canonicalizeIncomingMoney(record, this.moneyFields);
15056
15571
  if (this.subsystemBus?.hasGateHandlers("beforePut")) {
15057
15572
  const existingEnv = await this.adapter.get(this.vault, this.name, id);
15058
15573
  let existingRecord = null;
@@ -15069,7 +15584,7 @@ var Collection = class {
15069
15584
  collection: this.name,
15070
15585
  docId: id,
15071
15586
  incoming: record,
15072
- existing: existingRecord,
15587
+ existing: canonicalizeStoredMoney(existingRecord, this.moneyFields),
15073
15588
  existingVersion: existingEnv?._v ?? 0,
15074
15589
  existingTs: existingEnv?._ts,
15075
15590
  userId: this.keyring.userId,
@@ -15338,7 +15853,7 @@ var Collection = class {
15338
15853
  */
15339
15854
  async dispatchDerivations(id, record, version) {
15340
15855
  if (this.derivationSource === void 0) return;
15341
- const incoming = record;
15856
+ const incoming = canonicalizeStoredMoney(record, this.moneyFields);
15342
15857
  if (incoming && typeof incoming === "object" && "_derivedFrom" in incoming) return;
15343
15858
  const registry = this.derivationSource.registry();
15344
15859
  const strategies = registry.strategiesForSource(this.name);
@@ -15350,9 +15865,18 @@ var Collection = class {
15350
15865
  if (DerivationExecutor2 === null) {
15351
15866
  ({ DerivationExecutor: DerivationExecutor2 } = await Promise.resolve().then(() => (init_executor2(), executor_exports2)));
15352
15867
  }
15353
- const sourceWithId = { ...incoming, id };
15868
+ let sourceWithId;
15869
+ let sourceVersion = version;
15870
+ if (spec.source === this.name) {
15871
+ sourceWithId = { ...incoming, id };
15872
+ } else {
15873
+ const primary = await this.derivationSource.getCollection(spec.source).get(id);
15874
+ if (primary === null || primary === void 0) continue;
15875
+ sourceWithId = { ...primary, id };
15876
+ sourceVersion = 0;
15877
+ }
15354
15878
  const ctx = { vault: this.derivationSource.getReadOnlyFacade() };
15355
- const result = await DerivationExecutor2.run(spec, sourceWithId, version, strategyHash, ctx);
15879
+ const result = await DerivationExecutor2.run(spec, sourceWithId, sourceVersion, strategyHash, ctx);
15356
15880
  for (const key of Object.keys(spec.outputs)) {
15357
15881
  const out = result.outputs[key];
15358
15882
  if (!out) continue;
@@ -15571,7 +16095,7 @@ var Collection = class {
15571
16095
  vault: this.vault,
15572
16096
  collection: this.name,
15573
16097
  docId: id,
15574
- existing: existingRecord,
16098
+ existing: canonicalizeStoredMoney(existingRecord, this.moneyFields),
15575
16099
  existingVersion: existingEnv._v,
15576
16100
  existingTs: existingEnv._ts,
15577
16101
  internal,
@@ -17335,37 +17859,34 @@ var OverlayedCollection = class {
17335
17859
  /** Get the merged row by id. */
17336
17860
  async get(id) {
17337
17861
  const overlayRow = await this.overlayCollection.get(id);
17338
- if (overlayRow !== null && this.shadowPredicateApplies(overlayRow)) {
17339
- return overlayRow;
17340
- }
17341
17862
  const baseRow = await this.baseCollection.get(id);
17342
- if (baseRow !== null) return baseRow;
17343
- return null;
17863
+ return this.mergeRows(overlayRow, baseRow);
17344
17864
  }
17345
17865
  /** List union of base + overlay ids, applying the merge per row. */
17346
17866
  async list() {
17347
17867
  const baseRows = await this.baseCollection.list();
17348
17868
  const overlayRows = await this.overlayCollection.list();
17349
- const merged = /* @__PURE__ */ new Map();
17350
17869
  const idOf = (row) => {
17351
17870
  if (this.baseRowKey) return this.baseRowKey(row);
17352
17871
  const idField = row.id;
17353
17872
  return typeof idField === "string" ? idField : "";
17354
17873
  };
17874
+ const baseById = /* @__PURE__ */ new Map();
17875
+ const overlayById = /* @__PURE__ */ new Map();
17355
17876
  for (const row of baseRows) {
17356
17877
  const id = idOf(row);
17357
- if (id) merged.set(id, row);
17878
+ if (id) baseById.set(id, row);
17358
17879
  }
17359
17880
  for (const row of overlayRows) {
17360
17881
  const id = idOf(row);
17361
- if (!id) continue;
17362
- if (this.shadowPredicateApplies(row)) {
17363
- merged.set(id, row);
17364
- } else if (!merged.has(id)) {
17365
- continue;
17366
- }
17882
+ if (id) overlayById.set(id, row);
17367
17883
  }
17368
- return [...merged.values()];
17884
+ const out = [];
17885
+ for (const id of /* @__PURE__ */ new Set([...baseById.keys(), ...overlayById.keys()])) {
17886
+ const merged = this.mergeRows(overlayById.get(id) ?? null, baseById.get(id) ?? null);
17887
+ if (merged !== null) out.push(merged);
17888
+ }
17889
+ return out;
17369
17890
  }
17370
17891
  /**
17371
17892
  * Write to the overlay. Two forms:
@@ -17405,9 +17926,42 @@ var OverlayedCollection = class {
17405
17926
  async delete(id) {
17406
17927
  await this.overlayCollection.delete(id);
17407
17928
  }
17408
- /** True when `overlay[shadowField] === shadowValue`. */
17409
- shadowPredicateApplies(row) {
17410
- return row[this.spec.shadowField] === this.spec.shadowValue;
17929
+ /**
17930
+ * Merge a single id's overlay + base rows into the visible row.
17931
+ *
17932
+ * Priority (first match wins):
17933
+ * 1. Binary shadow win — overlay present AND
17934
+ * `overlay[shadowField] === shadowValue` → return the overlay row
17935
+ * entirely. This stays FIRST so the original binary behaviour is
17936
+ * unchanged whether or not `mergeMode` is configured.
17937
+ * 2. Field-level merge — overlay present, `mergeMode` configured,
17938
+ * and a rule whose `whenStatus` equals `overlay[shadowField]`.
17939
+ * The matched rule pulls its `overlayFields` (those present on
17940
+ * the overlay row) on top of the base row. With no base row, the
17941
+ * overlay row is returned as-is.
17942
+ * 3. Fallback — return the base row (possibly `null`). An
17943
+ * overlay-only row that qualifies under neither (1) nor (2) and
17944
+ * has no base is therefore NOT surfaced.
17945
+ */
17946
+ mergeRows(overlayRow, baseRow) {
17947
+ const shadowField = this.spec.shadowField;
17948
+ if (overlayRow !== null && overlayRow[shadowField] === this.spec.shadowValue) {
17949
+ return overlayRow;
17950
+ }
17951
+ if (overlayRow !== null && this.spec.mergeMode) {
17952
+ const status = overlayRow[shadowField];
17953
+ const rule = this.spec.mergeMode.rules.find((r) => r.whenStatus === status);
17954
+ if (rule) {
17955
+ if (baseRow === null) return overlayRow;
17956
+ const overlaySrc = overlayRow;
17957
+ const picked = {};
17958
+ for (const field of rule.overlayFields) {
17959
+ if (field in overlaySrc) picked[field] = overlaySrc[field];
17960
+ }
17961
+ return { ...baseRow, ...picked };
17962
+ }
17963
+ }
17964
+ return baseRow;
17411
17965
  }
17412
17966
  // ─── Throw-stubs for the unimplemented Collection<T> surface ───────
17413
17967
  //
@@ -19831,17 +20385,23 @@ var Vault = class {
19831
20385
  * const cur = await vault.sequence('invoice-2026').peek() // current value, no allocation
19832
20386
  * ```
19833
20387
  */
19834
- sequence(name) {
19835
- if (this.numberingConfigs.has(name)) {
20388
+ sequence(series, opts) {
20389
+ if (series.includes("\0")) {
20390
+ throw new ValidationError(`sequence("${series}"): series name must not contain a null byte (\\x00).`);
20391
+ }
20392
+ if (this.numberingConfigs.has(series)) {
19836
20393
  const eng = this.deferred();
19837
20394
  return {
19838
- next: async (opts) => {
19839
- if (!opts?.for) {
19840
- throw new ValidationError(`sequence("${name}") is a deferred-numbering series; call next({ for: recordId }).`);
20395
+ next: async (nextOpts) => {
20396
+ if (!nextOpts?.for) {
20397
+ throw new ValidationError(`sequence("${series}") is a deferred-numbering series; call next({ for: recordId }).`);
19841
20398
  }
19842
- return (await eng.enqueue(name, opts.for)).assigned;
20399
+ return (await eng.enqueue(series, nextOpts.for)).assigned;
19843
20400
  },
19844
- peek: () => eng.peek(name)
20401
+ peek: () => eng.peek(series),
20402
+ seedTo: () => {
20403
+ throw new ValidationError(`sequence("${series}") is a deferred-numbering series; seedTo is CAS-only.`);
20404
+ }
19845
20405
  };
19846
20406
  }
19847
20407
  if (!this.sequenceStore) {
@@ -19853,7 +20413,7 @@ var Vault = class {
19853
20413
  actor: this.keyring.userId
19854
20414
  });
19855
20415
  }
19856
- return this.sequenceStore.handle(name);
20416
+ return this.sequenceStore.handle(resolveSequenceKey(series, opts));
19857
20417
  }
19858
20418
  /** @internal — lazily build the deferred-numbering engine with a cache-coherent stamp. */
19859
20419
  deferred() {
@@ -20145,9 +20705,24 @@ var Vault = class {
20145
20705
  });
20146
20706
  }
20147
20707
  if (rule.mode === "cascade") {
20708
+ const txCtx = this.noydb._activeTxContextOrNull;
20148
20709
  for (const match of matches) {
20149
20710
  const matchId = match["id"] ?? null;
20150
20711
  if (matchId === null) continue;
20712
+ if (txCtx !== null) {
20713
+ const prior = await this.adapter.get(this.name, rule.collection, matchId);
20714
+ if (prior !== null) {
20715
+ txCtx._executed.push({
20716
+ op: {
20717
+ type: "delete",
20718
+ vaultName: this.name,
20719
+ collectionName: rule.collection,
20720
+ id: matchId
20721
+ },
20722
+ priorEnvelope: prior
20723
+ });
20724
+ }
20725
+ }
20151
20726
  await fromCollection.delete(matchId);
20152
20727
  }
20153
20728
  }
@@ -22346,7 +22921,7 @@ var NOT_ENABLED6 = new Error(
22346
22921
  'Multi-record transactions require the tx strategy. Import `{ withTransactions }` from "@noy-db/hub/tx" and pass it to `createNoydb({ txStrategy: withTransactions() })`.'
22347
22922
  );
22348
22923
  var NO_TX = {
22349
- async runTransaction() {
22924
+ async runTransaction(_db, _fn, _options, _txInvariants) {
22350
22925
  throw NOT_ENABLED6;
22351
22926
  },
22352
22927
  async runDryRun() {
@@ -25664,7 +26239,7 @@ function recordId2(record) {
25664
26239
  return typeof id === "string" ? id : "";
25665
26240
  }
25666
26241
  function immutableGuard(config) {
25667
- const { collection, after, appendOnly, amendmentRoles } = config;
26242
+ const { collection, after, appendOnly, amendmentRoles, amendmentInvariant } = config;
25668
26243
  if (appendOnly && after !== void 0) {
25669
26244
  throw new ValidationError("immutableGuard: `after` and `appendOnly` are mutually exclusive");
25670
26245
  }
@@ -25690,12 +26265,16 @@ function immutableGuard(config) {
25690
26265
  }
25691
26266
  },
25692
26267
  // The authorized override: inside an amendment transaction the
25693
- // check/onDelete are skipped and the change is ledgered. No extra
25694
- // invariant — the amendment itself is the sanctioned exception.
26268
+ // check/onDelete are skipped and the change is ledgered. By default
26269
+ // there is no extra invariant — the amendment itself is the
26270
+ // sanctioned exception. Callers may supply `amendmentInvariant` to
26271
+ // keep a constraint inviolable even under amendment (e.g. forbid
26272
+ // deletes, or preserve a cross-record sum); a throw reverts the
26273
+ // amendment and surfaces as `InvariantError`.
25695
26274
  amendment: {
25696
26275
  roles: amendmentRoles ?? ["admin", "owner"],
25697
- invariant: () => {
25698
- }
26276
+ invariant: amendmentInvariant ?? (() => {
26277
+ })
25699
26278
  }
25700
26279
  };
25701
26280
  return withGuard(spec);
@@ -25716,6 +26295,18 @@ function withDerivation(spec) {
25716
26295
  if (typeof spec.derive !== "function") {
25717
26296
  throw new ValidationError("withDerivation: derive must be a function");
25718
26297
  }
26298
+ if (spec.sources !== void 0) {
26299
+ for (const extra of spec.sources) {
26300
+ if (typeof extra !== "string" || extra.length === 0) {
26301
+ throw new ValidationError("withDerivation: each entry in sources[] must be a non-empty string");
26302
+ }
26303
+ if (extra === spec.source) {
26304
+ throw new ValidationError(
26305
+ `withDerivation: sources[] must not contain the primary source "${spec.source}"`
26306
+ );
26307
+ }
26308
+ }
26309
+ }
25719
26310
  const lifecycleMode = typeof spec.lifecycle === "string" ? spec.lifecycle : spec.lifecycle.mode;
25720
26311
  for (const [outputKey, outputSpec] of Object.entries(spec.outputs)) {
25721
26312
  if (outputSpec.shape === "array") {
@@ -25768,9 +26359,9 @@ function withMaterializedView(spec) {
25768
26359
  throw new ValidationError("withMaterializedView: query must be a function returning a Query<T>");
25769
26360
  }
25770
26361
  if (spec.unionSources) {
25771
- if (spec.unionSources.length < 2) {
26362
+ if (spec.unionSources.length < 1) {
25772
26363
  throw new MaterializedViewConfigError(
25773
- "unionSources requires at least 2 source collections"
26364
+ "unionSources requires at least 1 source collection"
25774
26365
  );
25775
26366
  }
25776
26367
  const seen = /* @__PURE__ */ new Set();
@@ -25802,6 +26393,16 @@ function withMaterializedView(spec) {
25802
26393
  `withMaterializedView "${spec.name}": UNION strategy with aggregate requires groupBy \u2014 use groupBy to declare the bucketing keys, or remove aggregate for a pure dedup MV`
25803
26394
  );
25804
26395
  }
26396
+ if (spec.moneyFields && !spec.aggregate) {
26397
+ throw new MaterializedViewConfigError(
26398
+ `withMaterializedView "${spec.name}": moneyFields requires aggregate \u2014 moneyFields rewrites sum/min/max reducers over money output fields, so it is meaningless without an aggregate spec`
26399
+ );
26400
+ }
26401
+ if (spec.unionSources.some((s) => s.join && s.join.length > 0) && (!spec.sources || spec.sources.length === 0)) {
26402
+ throw new MaterializedViewConfigError(
26403
+ `withMaterializedView "${spec.name}": a unionSources arm declares join(s) but no \`sources\` are listed \u2014 declare sources: [...] with the right-side (join-target) collection names so writes to them trigger MV refresh`
26404
+ );
26405
+ }
25805
26406
  if (spec.predicates) {
25806
26407
  throw new MaterializedViewConfigError(
25807
26408
  `withMaterializedView "${spec.name}": predicates are not supported on UNION strategies \u2014 UNION mode does not use a Query<T> chain, so .wherePredicate() cannot fire. Use the query() form, or open an issue if per-arm predicates are needed`
@@ -25862,6 +26463,117 @@ init_errors();
25862
26463
  init_descriptor();
25863
26464
  init_iso4217();
25864
26465
 
26466
+ // src/money/arith.ts
26467
+ init_fixed_point();
26468
+ init_descriptor();
26469
+ function parseAmount(label, amount, scale, rounding) {
26470
+ const r = parseToScaledInt(amount, scale, rounding);
26471
+ if (!r.ok) {
26472
+ throw new MoneyUnsupportedError(
26473
+ r.reason === "precision" ? `${label}: amount ${JSON.stringify(amount)} has more precision than scale ${scale} and no rounding mode is configured` : `${label}: amount ${JSON.stringify(amount)} is not a finite decimal`
26474
+ );
26475
+ }
26476
+ return r.value;
26477
+ }
26478
+ function resolveScale(label, amount, explicit) {
26479
+ if (explicit !== void 0) {
26480
+ if (!Number.isInteger(explicit) || explicit < 0) {
26481
+ throw new MoneyUnsupportedError(`${label}: scale must be a non-negative integer`);
26482
+ }
26483
+ return explicit;
26484
+ }
26485
+ const inferred = decimalScaleOf(amount);
26486
+ if (inferred === null) {
26487
+ throw new MoneyUnsupportedError(`${label}: amount ${JSON.stringify(amount)} is not a finite decimal`);
26488
+ }
26489
+ return inferred;
26490
+ }
26491
+ function mulRate(amount, rate, opts = {}) {
26492
+ const scale = resolveScale("mulRate", amount, opts.scale);
26493
+ const rounding = opts.rounding ?? "half-up";
26494
+ const a = parseAmount("mulRate", amount, scale, rounding);
26495
+ const rateScale = decimalScaleOf(rate);
26496
+ if (rateScale === null) {
26497
+ throw new MoneyUnsupportedError(`mulRate: rate ${JSON.stringify(rate)} is not a finite decimal`);
26498
+ }
26499
+ const r = parseToScaledInt(rate, rateScale);
26500
+ if (!r.ok) {
26501
+ throw new MoneyUnsupportedError(`mulRate: rate ${JSON.stringify(rate)} is not a finite decimal`);
26502
+ }
26503
+ const product = a * r.value;
26504
+ return formatScaledInt(rescaleScaledInt(product, scale + rateScale, scale, rounding), scale);
26505
+ }
26506
+ function allocate(amount, weights, opts = {}) {
26507
+ if (weights.length === 0) {
26508
+ throw new MoneyUnsupportedError("allocate: weights must not be empty");
26509
+ }
26510
+ const scale = resolveScale("allocate", amount, opts.scale);
26511
+ const a = parseAmount("allocate", amount, scale);
26512
+ let weightScale = 0;
26513
+ for (const w of weights) {
26514
+ const s = decimalScaleOf(w);
26515
+ if (s === null) {
26516
+ throw new MoneyUnsupportedError(`allocate: weight ${JSON.stringify(w)} is not a finite decimal`);
26517
+ }
26518
+ if (s > weightScale) weightScale = s;
26519
+ }
26520
+ const scaledWeights = weights.map((w) => {
26521
+ const r = parseToScaledInt(w, weightScale);
26522
+ if (!r.ok || r.value < 0n) {
26523
+ throw new MoneyUnsupportedError(`allocate: weight ${JSON.stringify(w)} must be a non-negative decimal`);
26524
+ }
26525
+ return r.value;
26526
+ });
26527
+ const sumW = scaledWeights.reduce((acc, w) => acc + w, 0n);
26528
+ if (sumW === 0n) {
26529
+ throw new MoneyUnsupportedError("allocate: weights must not all be zero");
26530
+ }
26531
+ const negative = a < 0n;
26532
+ const mag = negative ? -a : a;
26533
+ const base = [];
26534
+ const remainders = [];
26535
+ let distributed = 0n;
26536
+ for (let i = 0; i < scaledWeights.length; i++) {
26537
+ const product = mag * scaledWeights[i];
26538
+ const share = product / sumW;
26539
+ base.push(share);
26540
+ distributed += share;
26541
+ remainders.push({ index: i, rem: product % sumW });
26542
+ }
26543
+ let leftover = mag - distributed;
26544
+ remainders.sort((x, y) => y.rem > x.rem ? 1 : y.rem < x.rem ? -1 : x.index - y.index);
26545
+ for (const { index } of remainders) {
26546
+ if (leftover === 0n) break;
26547
+ base[index] = base[index] + 1n;
26548
+ leftover -= 1n;
26549
+ }
26550
+ return base.map((p) => formatScaledInt(negative && p !== 0n ? -p : p, scale));
26551
+ }
26552
+
26553
+ // src/money/branded.ts
26554
+ init_fixed_point();
26555
+ init_descriptor();
26556
+ function asMoney(value) {
26557
+ if (!isMoneyLike(value)) {
26558
+ throw new MoneyUnsupportedError(`asMoney: ${JSON.stringify(value)} is not a finite decimal`);
26559
+ }
26560
+ return String(value).trim();
26561
+ }
26562
+ function isMoneyString(value) {
26563
+ return typeof value === "string" && isMoneyLike(value);
26564
+ }
26565
+ function moneyNumber(value) {
26566
+ if (!isMoneyLike(value)) {
26567
+ throw new MoneyUnsupportedError(`moneyNumber: ${JSON.stringify(value)} is not a finite decimal`);
26568
+ }
26569
+ return Number(value);
26570
+ }
26571
+ function isMoneyLike(value) {
26572
+ if (typeof value === "number") return Number.isFinite(value);
26573
+ if (typeof value !== "string") return false;
26574
+ return decimalScaleOf(value) !== null;
26575
+ }
26576
+
25865
26577
  // src/i18n/script.ts
25866
26578
  init_errors();
25867
26579
  var LATIN_BASE = /* @__PURE__ */ new Set([
@@ -26804,9 +27516,11 @@ function shortJSON(value) {
26804
27516
  WeakPassphraseError,
26805
27517
  activeSessionCount,
26806
27518
  additiveOnly,
27519
+ allocate,
26807
27520
  applyI18nLocale,
26808
27521
  applyJoins,
26809
27522
  applyPatch,
27523
+ asMoney,
26810
27524
  assertStrongPassphrase,
26811
27525
  assertTierAccess,
26812
27526
  avg,
@@ -26883,6 +27597,7 @@ function shortJSON(value) {
26883
27597
  isI18nTextDescriptor,
26884
27598
  isMagicLinkGrantExpired,
26885
27599
  isMoneyDescriptor,
27600
+ isMoneyString,
26886
27601
  isPreCompressed,
26887
27602
  isPublicEnvelope,
26888
27603
  isSessionAlive,
@@ -26915,6 +27630,8 @@ function shortJSON(value) {
26915
27630
  mintShamirRecoveryEntry,
26916
27631
  mintWrappedDeksBlob,
26917
27632
  money,
27633
+ moneyNumber,
27634
+ mulRate,
26918
27635
  paddedIndex,
26919
27636
  parseBytes,
26920
27637
  parseIndex,
@@ -26941,6 +27658,7 @@ function shortJSON(value) {
26941
27658
  resolveI18nText,
26942
27659
  resolvePolicy,
26943
27660
  resolvePublicEnvelopeSchema,
27661
+ resolveSequenceKey,
26944
27662
  resolveSession,
26945
27663
  revokeAllSessions,
26946
27664
  revokeDelegation,