@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
@@ -57,88 +57,156 @@ __export(query_exports, {
57
57
  });
58
58
  module.exports = __toCommonJS(query_exports);
59
59
 
60
- // src/query/predicate.ts
61
- function readPath(record, path) {
62
- if (record === null || record === void 0) return void 0;
63
- if (!path.includes(".")) {
64
- 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);
65
77
  }
66
- const segments = path.split(".");
67
- let cursor = record;
68
- for (const segment of segments) {
69
- if (cursor === null || cursor === void 0) return void 0;
70
- 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();
71
87
  }
72
- 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;
73
92
  }
74
- function evaluateFieldClause(record, clause) {
75
- const actual = readPath(record, clause.field);
76
- const { op, value } = clause;
77
- switch (op) {
78
- case "==":
79
- return actual === value;
80
- case "!=":
81
- return actual !== value;
82
- case "<":
83
- return isComparable(actual, value) && actual < value;
84
- case "<=":
85
- return isComparable(actual, value) && actual <= value;
86
- case ">":
87
- return isComparable(actual, value) && actual > value;
88
- case ">=":
89
- return isComparable(actual, value) && actual >= value;
90
- case "in":
91
- return Array.isArray(value) && value.includes(actual);
92
- case "contains":
93
- if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
94
- if (Array.isArray(actual)) return actual.includes(value);
95
- return false;
96
- case "startsWith":
97
- return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
98
- case "between": {
99
- if (!Array.isArray(value) || value.length !== 2) return false;
100
- const [lo, hi] = value;
101
- if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
102
- return actual >= lo && actual <= hi;
103
- }
104
- default: {
105
- const _exhaustive = op;
106
- void _exhaustive;
93
+ function shouldRoundUp(negative, lastKeptDigit, firstDiscarded, hasMoreNonZeroAfterFirst, mode) {
94
+ switch (mode) {
95
+ case "up":
96
+ return true;
97
+ case "down":
107
98
  return false;
108
- }
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;
109
111
  }
110
112
  }
111
- function isComparable(a, b) {
112
- if (typeof a === "number" && typeof b === "number") return true;
113
- if (typeof a === "string" && typeof b === "string") return true;
114
- if (a instanceof Date && b instanceof Date) return true;
115
- 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 };
116
142
  }
117
- function evaluateClause(record, clause) {
118
- switch (clause.type) {
119
- case "field":
120
- return evaluateFieldClause(record, clause);
121
- case "filter":
122
- return clause.fn(record);
123
- case "wherePredicate":
124
- return clause.fn(record, clause.ctx);
125
- case "crossJoin":
126
- throw new Error(
127
- `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.`
128
- );
129
- case "group":
130
- if (clause.op === "and") {
131
- for (const child of clause.clauses) {
132
- if (!evaluateClause(record, child)) return false;
133
- }
134
- return true;
135
- } else {
136
- for (const child of clause.clauses) {
137
- if (evaluateClause(record, child)) return true;
138
- }
139
- return false;
140
- }
141
- }
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;
142
210
  }
143
211
 
144
212
  // src/errors.ts
@@ -151,6 +219,12 @@ var NoydbError = class extends Error {
151
219
  this.code = code;
152
220
  }
153
221
  };
222
+ var ValidationError = class extends NoydbError {
223
+ constructor(message = "Validation error") {
224
+ super("VALIDATION_ERROR", message);
225
+ this.name = "ValidationError";
226
+ }
227
+ };
154
228
  var GroupCardinalityError = class extends NoydbError {
155
229
  /** The field being grouped on. */
156
230
  field;
@@ -256,6 +330,244 @@ var DanglingReferenceError = class extends NoydbError {
256
330
  }
257
331
  };
258
332
 
333
+ // src/money/descriptor.ts
334
+ var MoneyUnsupportedError = class extends NoydbError {
335
+ constructor(field, message) {
336
+ super(
337
+ "MONEY_UNSUPPORTED",
338
+ message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
339
+ );
340
+ this.field = field;
341
+ this.name = "MoneyUnsupportedError";
342
+ }
343
+ field;
344
+ };
345
+
346
+ // src/money/where.ts
347
+ function isMoneyValueObject(v) {
348
+ return typeof v === "object" && v !== null && "currency" in v;
349
+ }
350
+ function parseOperand(field, raw, desc) {
351
+ let amount;
352
+ let currency;
353
+ if (desc.mode === "fixed") {
354
+ currency = desc.fixedCurrency;
355
+ amount = raw;
356
+ } else if (isMoneyValueObject(raw)) {
357
+ currency = String(raw.currency);
358
+ amount = raw.amount;
359
+ } else {
360
+ const sole = desc.soleCurrency();
361
+ if (sole === void 0) {
362
+ throw new MoneyUnsupportedError(
363
+ `where("${field}"): field is multi-currency \u2014 compare against { amount, currency }, not a bare amount`
364
+ );
365
+ }
366
+ currency = sole;
367
+ amount = raw;
368
+ }
369
+ if (typeof amount !== "number" && typeof amount !== "string") {
370
+ throw new MoneyUnsupportedError(
371
+ `where("${field}"): operand ${JSON.stringify(raw)} is not a money amount`
372
+ );
373
+ }
374
+ const r = parseToScaledInt(amount, desc.scaleFor(currency), desc.rounding);
375
+ if (!r.ok) {
376
+ throw new MoneyUnsupportedError(
377
+ `where("${field}"): operand ${JSON.stringify(amount)} is not a finite decimal`
378
+ );
379
+ }
380
+ return { scaled: r.value.toString(), currency };
381
+ }
382
+ function moneyFieldClause(field, op, value, desc) {
383
+ switch (op) {
384
+ case "==":
385
+ case "!=":
386
+ case "<":
387
+ case "<=":
388
+ case ">":
389
+ case ">=": {
390
+ const e = parseOperand(field, value, desc);
391
+ return withMoney(field, op, value, desc, [e]);
392
+ }
393
+ case "between": {
394
+ if (!Array.isArray(value) || value.length !== 2) {
395
+ throw new MoneyUnsupportedError(`where("${field}"): 'between' needs a [lo, hi] tuple`);
396
+ }
397
+ const lo = parseOperand(field, value[0], desc);
398
+ const hi = parseOperand(field, value[1], desc);
399
+ if (lo.currency !== hi.currency) {
400
+ throw new MoneyUnsupportedError(
401
+ `where("${field}"): 'between' bounds mix currencies (${lo.currency} vs ${hi.currency})`
402
+ );
403
+ }
404
+ return withMoney(field, op, value, desc, [lo, hi]);
405
+ }
406
+ case "in": {
407
+ if (!Array.isArray(value)) {
408
+ throw new MoneyUnsupportedError(`where("${field}"): 'in' needs an array of amounts`);
409
+ }
410
+ return withMoney(field, op, value, desc, value.map((v) => parseOperand(field, v, desc)));
411
+ }
412
+ default:
413
+ throw new MoneyUnsupportedError(
414
+ `where("${field}"): operator '${op}' is not supported on a money field`
415
+ );
416
+ }
417
+ }
418
+ function withMoney(field, op, originalValue, desc, entries) {
419
+ const money = { mode: desc.mode, entries };
420
+ const value = desc.mode !== "fixed" ? originalValue : entries.length === 1 && op !== "in" && op !== "between" ? entries[0].scaled : entries.map((e) => e.scaled);
421
+ return { type: "field", field, op, value, money };
422
+ }
423
+ function readStored(actual, operand) {
424
+ let amount;
425
+ let currency;
426
+ if (operand.mode === "fixed") {
427
+ if (typeof actual !== "string" && typeof actual !== "number") return null;
428
+ amount = actual;
429
+ currency = operand.entries[0]?.currency ?? "";
430
+ } else {
431
+ if (!isMoneyValueObject(actual)) return null;
432
+ if (typeof actual.currency !== "string") return null;
433
+ amount = actual.amount;
434
+ currency = actual.currency;
435
+ }
436
+ if (typeof amount !== "string" && typeof amount !== "number") return null;
437
+ try {
438
+ return { scaled: BigInt(amount).toString(), currency };
439
+ } catch {
440
+ return null;
441
+ }
442
+ }
443
+ function evaluateMoneyClause(actual, op, operand) {
444
+ const stored = readStored(actual, operand);
445
+ if (stored === null) return op === "!=";
446
+ const a = BigInt(stored.scaled);
447
+ if (op === "in") {
448
+ return operand.entries.some(
449
+ (e2) => e2.currency === stored.currency && BigInt(e2.scaled) === a
450
+ );
451
+ }
452
+ if (op === "between") {
453
+ const [lo, hi] = operand.entries;
454
+ if (!lo || !hi || lo.currency !== stored.currency) return false;
455
+ return a >= BigInt(lo.scaled) && a <= BigInt(hi.scaled);
456
+ }
457
+ const e = operand.entries[0];
458
+ if (!e) return false;
459
+ if (e.currency !== stored.currency) return op === "!=";
460
+ const b = BigInt(e.scaled);
461
+ switch (op) {
462
+ case "==":
463
+ return a === b;
464
+ case "!=":
465
+ return a !== b;
466
+ case "<":
467
+ return a < b;
468
+ case "<=":
469
+ return a <= b;
470
+ case ">":
471
+ return a > b;
472
+ case ">=":
473
+ return a >= b;
474
+ default:
475
+ return false;
476
+ }
477
+ }
478
+
479
+ // src/query/predicate.ts
480
+ function readPath(record, path) {
481
+ if (record === null || record === void 0) return void 0;
482
+ if (!path.includes(".")) {
483
+ return record[path];
484
+ }
485
+ const segments = path.split(".");
486
+ let cursor = record;
487
+ for (const segment of segments) {
488
+ if (cursor === null || cursor === void 0) return void 0;
489
+ cursor = cursor[segment];
490
+ }
491
+ return cursor;
492
+ }
493
+ function evaluateFieldClause(record, clause) {
494
+ const actual = readPath(record, clause.field);
495
+ const { op, value } = clause;
496
+ if (clause.money) return evaluateMoneyClause(actual, op, clause.money);
497
+ switch (op) {
498
+ case "==":
499
+ return actual === value;
500
+ case "!=":
501
+ return actual !== value;
502
+ case "<":
503
+ return isComparable(actual, value) && actual < value;
504
+ case "<=":
505
+ return isComparable(actual, value) && actual <= value;
506
+ case ">":
507
+ return isComparable(actual, value) && actual > value;
508
+ case ">=":
509
+ return isComparable(actual, value) && actual >= value;
510
+ case "in":
511
+ return Array.isArray(value) && value.includes(actual);
512
+ case "contains":
513
+ if (typeof actual === "string") return typeof value === "string" && actual.includes(value);
514
+ if (Array.isArray(actual)) return actual.includes(value);
515
+ return false;
516
+ case "startsWith":
517
+ return typeof actual === "string" && typeof value === "string" && actual.startsWith(value);
518
+ case "between": {
519
+ if (!Array.isArray(value) || value.length !== 2) return false;
520
+ const [lo, hi] = value;
521
+ if (!isComparable(actual, lo) || !isComparable(actual, hi)) return false;
522
+ return actual >= lo && actual <= hi;
523
+ }
524
+ default: {
525
+ const _exhaustive = op;
526
+ void _exhaustive;
527
+ return false;
528
+ }
529
+ }
530
+ }
531
+ function isComparable(a, b) {
532
+ if (typeof a === "number" && typeof b === "number") return true;
533
+ if (typeof a === "string" && typeof b === "string") return true;
534
+ if (a instanceof Date && b instanceof Date) return true;
535
+ return false;
536
+ }
537
+ function evaluateClause(record, clause, fnRecord) {
538
+ switch (clause.type) {
539
+ case "field":
540
+ return evaluateFieldClause(record, clause);
541
+ case "filter":
542
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record);
543
+ case "wherePredicate":
544
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record, clause.ctx);
545
+ case "crossJoin":
546
+ throw new Error(
547
+ `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.`
548
+ );
549
+ case "group":
550
+ if (clause.op === "and") {
551
+ for (const child of clause.clauses) {
552
+ if (!evaluateClause(record, child, fnRecord)) return false;
553
+ }
554
+ return true;
555
+ } else {
556
+ for (const child of clause.clauses) {
557
+ if (evaluateClause(record, child, fnRecord)) return true;
558
+ }
559
+ return false;
560
+ }
561
+ }
562
+ }
563
+ function hasFnClause(clauses) {
564
+ for (const c of clauses) {
565
+ if (c.type === "filter" || c.type === "wherePredicate") return true;
566
+ if (c.type === "group" && hasFnClause(c.clauses)) return true;
567
+ }
568
+ return false;
569
+ }
570
+
259
571
  // src/query/join.ts
260
572
  var DEFAULT_JOIN_MAX_ROWS = 5e4;
261
573
  var JOIN_WARN_FRACTION = 0.8;
@@ -501,97 +813,23 @@ var NO_AGGREGATE = {
501
813
  }
502
814
  };
503
815
 
504
- // src/money/fixed-point.ts
505
- function formatScaledInt(value, scale) {
506
- const negative = value < 0n;
507
- const abs = (negative ? -value : value).toString();
508
- if (scale === 0) return (negative ? "-" : "") + abs;
509
- const padded = abs.padStart(scale + 1, "0");
510
- const cut = padded.length - scale;
511
- const intPart = padded.slice(0, cut);
512
- const fracPart = padded.slice(cut);
513
- return (negative ? "-" : "") + intPart + "." + fracPart;
514
- }
515
-
516
- // src/money/iso4217.ts
517
- var MINOR_UNITS = {
518
- // 2-decimal majors
519
- EUR: 2,
520
- USD: 2,
521
- GBP: 2,
522
- CHF: 2,
523
- CAD: 2,
524
- AUD: 2,
525
- NZD: 2,
526
- SGD: 2,
527
- HKD: 2,
528
- CNY: 2,
529
- INR: 2,
530
- BRL: 2,
531
- MXN: 2,
532
- ZAR: 2,
533
- RUB: 2,
534
- TRY: 2,
535
- PLN: 2,
536
- SEK: 2,
537
- NOK: 2,
538
- DKK: 2,
539
- CZK: 2,
540
- HUF: 2,
541
- RON: 2,
542
- ILS: 2,
543
- THB: 2,
544
- PHP: 2,
545
- MYR: 2,
546
- IDR: 2,
547
- AED: 2,
548
- SAR: 2,
549
- QAR: 2,
550
- EGP: 2,
551
- // 0-decimal
552
- JPY: 0,
553
- KRW: 0,
554
- ISK: 0,
555
- CLP: 0,
556
- VND: 0,
557
- XOF: 0,
558
- XAF: 0,
559
- PYG: 0,
560
- // 3-decimal
561
- BHD: 3,
562
- KWD: 3,
563
- OMR: 3,
564
- TND: 3,
565
- JOD: 3,
566
- IQD: 3,
567
- LYD: 3
568
- };
569
- function scaleForCurrency(code) {
570
- const v = MINOR_UNITS[code];
571
- return v === void 0 ? null : v;
572
- }
573
-
574
- // src/money/descriptor.ts
575
- var MoneyUnsupportedError = class extends NoydbError {
576
- constructor(field, message) {
577
- super(
578
- "MONEY_UNSUPPORTED",
579
- message ?? `money: operation is not supported on field "${field}" \u2014 use sum() and count() and divide at the boundary`
580
- );
581
- this.field = field;
582
- this.name = "MoneyUnsupportedError";
583
- }
584
- field;
585
- };
586
-
587
816
  // src/money/money-reducer.ts
588
- function toScaledInt(v) {
589
- if (typeof v === "string" || typeof v === "number" || typeof v === "bigint") {
590
- try {
591
- return BigInt(v);
592
- } catch {
593
- return null;
817
+ function toScaledIntFromAny(v, scale) {
818
+ if (typeof v === "bigint") return v;
819
+ if (typeof v === "number") {
820
+ const r = parseToScaledInt(v, scale);
821
+ return r.ok ? r.value : null;
822
+ }
823
+ if (typeof v === "string") {
824
+ if (!v.includes(".")) {
825
+ try {
826
+ return BigInt(v);
827
+ } catch {
828
+ return null;
829
+ }
594
830
  }
831
+ const r = parseToScaledInt(v, scale);
832
+ return r.ok ? r.value : null;
595
833
  }
596
834
  return null;
597
835
  }
@@ -599,13 +837,15 @@ function readMoney(record, field, desc) {
599
837
  const raw = readPath(record, field);
600
838
  if (raw === null || raw === void 0) return null;
601
839
  if (desc.mode === "fixed") {
602
- const value2 = toScaledInt(raw);
603
- return value2 === null ? null : { currency: desc.fixedCurrency, value: value2 };
840
+ const cur = desc.fixedCurrency;
841
+ const value2 = toScaledIntFromAny(raw, desc.scaleFor(cur));
842
+ return value2 === null ? null : { currency: cur, value: value2 };
604
843
  }
605
844
  if (typeof raw !== "object") return null;
606
845
  const o = raw;
607
846
  if (typeof o.currency !== "string") return null;
608
- const value = toScaledInt(o.amount);
847
+ const scale = desc.allows(o.currency) ? desc.scaleFor(o.currency) : 0;
848
+ const value = toScaledIntFromAny(o.amount, scale);
609
849
  return value === null ? null : { currency: o.currency, value };
610
850
  }
611
851
  function targetScaleFor(desc, currency) {
@@ -758,8 +998,107 @@ function wrapMoneyReducers(spec, moneyFields) {
758
998
  return changed ? out : spec;
759
999
  }
760
1000
 
1001
+ // src/money/paths.ts
1002
+ var SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
1003
+ var parseCache = /* @__PURE__ */ new Map();
1004
+ function parseMoneyPath(path) {
1005
+ const cached = parseCache.get(path);
1006
+ if (cached) return cached;
1007
+ if (typeof path !== "string" || path.length === 0) {
1008
+ throw new ValidationError("moneyFields: path must be a non-empty string");
1009
+ }
1010
+ const segments = [];
1011
+ for (const part of path.split(".")) {
1012
+ const m = SEGMENT_RE.exec(part);
1013
+ if (!m) {
1014
+ throw new ValidationError(
1015
+ `moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
1016
+ );
1017
+ }
1018
+ const array = m[2] === "[]";
1019
+ segments.push(
1020
+ m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
1021
+ );
1022
+ }
1023
+ parseCache.set(path, segments);
1024
+ return segments;
1025
+ }
1026
+ function isSimpleMoneyPath(path) {
1027
+ return !path.includes(".") && !path.includes("[") && !path.includes("*");
1028
+ }
1029
+ function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
1030
+ if (node === null || node === void 0) return node;
1031
+ const seg = segments[index];
1032
+ const last = index === segments.length - 1;
1033
+ if (seg.kind === "key") {
1034
+ if (typeof node !== "object" || Array.isArray(node)) {
1035
+ if (lenient) return node;
1036
+ throw new ValidationError(
1037
+ `moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
1038
+ );
1039
+ }
1040
+ const obj2 = node;
1041
+ if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
1042
+ if (seg.array) {
1043
+ const arr = obj2[seg.key];
1044
+ if (!Array.isArray(arr)) {
1045
+ if (lenient) return node;
1046
+ throw new ValidationError(
1047
+ `moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
1048
+ );
1049
+ }
1050
+ const cloned = [...arr];
1051
+ if (last) {
1052
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
1053
+ } else {
1054
+ for (let i = 0; i < cloned.length; i++) {
1055
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
1056
+ }
1057
+ }
1058
+ return { ...obj2, [seg.key]: cloned };
1059
+ }
1060
+ const clone2 = { ...obj2 };
1061
+ if (last) {
1062
+ visit(clone2, seg.key);
1063
+ } else {
1064
+ clone2[seg.key] = transformAtMoneyPath(clone2[seg.key], path, segments, index + 1, visit, lenient);
1065
+ }
1066
+ return clone2;
1067
+ }
1068
+ if (seg.array) {
1069
+ if (!Array.isArray(node)) {
1070
+ if (lenient) return node;
1071
+ throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
1072
+ }
1073
+ const cloned = [...node];
1074
+ if (last) {
1075
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
1076
+ } else {
1077
+ for (let i = 0; i < cloned.length; i++) {
1078
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
1079
+ }
1080
+ }
1081
+ return cloned;
1082
+ }
1083
+ if (typeof node !== "object" || Array.isArray(node)) {
1084
+ if (lenient) return node;
1085
+ throw new ValidationError(
1086
+ `moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
1087
+ );
1088
+ }
1089
+ const obj = node;
1090
+ const clone = { ...obj };
1091
+ for (const key of Object.keys(obj)) {
1092
+ const v = clone[key];
1093
+ if (v === null || v === void 0) continue;
1094
+ if (last) visit(clone, key);
1095
+ else clone[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
1096
+ }
1097
+ return clone;
1098
+ }
1099
+
761
1100
  // src/money/normalize.ts
762
- function isMoneyValueObject(v) {
1101
+ function isMoneyValueObject2(v) {
763
1102
  return typeof v === "object" && v !== null && "currency" in v;
764
1103
  }
765
1104
  function formatCurrency(decimal, currency, scale, locale) {
@@ -771,33 +1110,70 @@ function formatCurrency(decimal, currency, scale, locale) {
771
1110
  });
772
1111
  return fmt.format(decimal);
773
1112
  }
1113
+ function decodeValue(stored, desc) {
1114
+ let currency;
1115
+ let scaledIntString;
1116
+ if (desc.mode === "fixed") {
1117
+ if (typeof stored !== "string" && typeof stored !== "number") return null;
1118
+ currency = desc.fixedCurrency;
1119
+ scaledIntString = String(stored);
1120
+ } else {
1121
+ if (!isMoneyValueObject2(stored)) return null;
1122
+ const amount = stored.amount;
1123
+ if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
1124
+ currency = stored.currency;
1125
+ scaledIntString = String(amount);
1126
+ }
1127
+ const scale = desc.scaleFor(currency);
1128
+ let decimal;
1129
+ try {
1130
+ decimal = formatScaledInt(BigInt(scaledIntString), scale);
1131
+ } catch {
1132
+ return null;
1133
+ }
1134
+ return {
1135
+ decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
1136
+ decimal,
1137
+ currency,
1138
+ scale
1139
+ };
1140
+ }
774
1141
  function decodeMoneyFields(record, moneyFields, locale) {
775
- const out = { ...record };
1142
+ let out = { ...record };
776
1143
  const format = locale !== "raw";
777
1144
  const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
778
- for (const [field, desc] of Object.entries(moneyFields)) {
779
- const stored = out[field];
780
- if (stored === null || stored === void 0) continue;
781
- let currency;
782
- let scaledIntString;
783
- if (desc.mode === "fixed") {
784
- if (typeof stored !== "string" && typeof stored !== "number") continue;
785
- currency = desc.fixedCurrency;
786
- scaledIntString = String(stored);
787
- } else {
788
- if (!isMoneyValueObject(stored)) continue;
789
- const amount = stored.amount;
790
- if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") continue;
791
- currency = stored.currency;
792
- scaledIntString = String(amount);
793
- }
794
- const scale = desc.scaleFor(currency);
795
- const decimal = formatScaledInt(BigInt(scaledIntString), scale);
796
- out[field] = desc.mode === "fixed" ? decimal : { amount: decimal, currency };
797
- if (format) {
798
- out[`${field}Formatted`] = formatCurrency(decimal, currency, scale, fmtLocale);
799
- out[`${field}Number`] = Number(decimal);
800
- }
1145
+ for (const [path, desc] of Object.entries(moneyFields)) {
1146
+ if (isSimpleMoneyPath(path)) {
1147
+ const stored = out[path];
1148
+ if (stored === null || stored === void 0) continue;
1149
+ const r = decodeValue(stored, desc);
1150
+ if (r === null) continue;
1151
+ out[path] = r.decoded;
1152
+ if (format) {
1153
+ out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
1154
+ out[`${path}Number`] = Number(r.decimal);
1155
+ }
1156
+ continue;
1157
+ }
1158
+ out = transformAtMoneyPath(
1159
+ out,
1160
+ path,
1161
+ parseMoneyPath(path),
1162
+ 0,
1163
+ (container, key) => {
1164
+ const stored = container[key];
1165
+ if (stored === null || stored === void 0) return;
1166
+ const r = decodeValue(stored, desc);
1167
+ if (r === null) return;
1168
+ container[key] = r.decoded;
1169
+ if (format && typeof key === "string" && !Array.isArray(container)) {
1170
+ container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
1171
+ container[`${key}Number`] = Number(r.decimal);
1172
+ }
1173
+ },
1174
+ /* lenient */
1175
+ true
1176
+ );
801
1177
  }
802
1178
  return out;
803
1179
  }
@@ -894,9 +1270,18 @@ var Query = class _Query {
894
1270
  this.predicates
895
1271
  );
896
1272
  }
897
- /** Add a field comparison. Multiple where() calls are AND-combined. */
1273
+ /**
1274
+ * Add a field comparison. Multiple where() calls are AND-combined.
1275
+ *
1276
+ * A declared money field compares in MAJOR units (#336): the operand
1277
+ * (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
1278
+ * quantized into stored scaled-int space at build time and evaluated
1279
+ * BigInt-exact per record. A malformed operand or a string operator
1280
+ * (`contains`/`startsWith`) throws here, at the call site.
1281
+ */
898
1282
  where(field, op, value) {
899
- const clause = { type: "field", field, op, value };
1283
+ const desc = this.source.moneyFields?.[field];
1284
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
900
1285
  return new _Query(
901
1286
  this.source,
902
1287
  { ...this.plan, clauses: [...this.plan.clauses, clause] },
@@ -1222,7 +1607,7 @@ var Query = class _Query {
1222
1607
  }
1223
1608
  const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
1224
1609
  if (remainingClauses.length === 0) return candidates.length;
1225
- return filterRecords(candidates, remainingClauses).length;
1610
+ return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
1226
1611
  }
1227
1612
  /**
1228
1613
  * Reduce the matching records through a named set of reducers.
@@ -1279,7 +1664,7 @@ var Query = class _Query {
1279
1664
  return executeClausePipeline(source, clauses, joinCtx);
1280
1665
  }
1281
1666
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
1282
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
1667
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
1283
1668
  };
1284
1669
  const upstreams = [];
1285
1670
  if (source.subscribe) {
@@ -1302,7 +1687,7 @@ var Query = class _Query {
1302
1687
  return executeClausePipeline(source, clauses, joinCtx);
1303
1688
  }
1304
1689
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
1305
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
1690
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
1306
1691
  };
1307
1692
  const upstreams = [];
1308
1693
  if (source.subscribe) {
@@ -1454,7 +1839,7 @@ function executePlanWithSource(source, plan, joinContext) {
1454
1839
  result = executeClausePipeline(source, plan.clauses, joinContext);
1455
1840
  } else {
1456
1841
  const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
1457
- result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses);
1842
+ result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
1458
1843
  }
1459
1844
  if (plan.orderBy.length > 0) {
1460
1845
  result = sortRecords(result, plan.orderBy);
@@ -1477,6 +1862,7 @@ function candidateRecords(source, clauses) {
1477
1862
  const clause = clauses[i];
1478
1863
  if (clause.type !== "field") continue;
1479
1864
  if (!indexes.has(clause.field)) continue;
1865
+ if (clause.money?.mode === "multi") continue;
1480
1866
  let ids = null;
1481
1867
  if (clause.op === "==") {
1482
1868
  ids = indexes.lookupEqual(clause.field, clause.value);
@@ -1522,13 +1908,20 @@ function executePlan(records, plan) {
1522
1908
  }
1523
1909
  return result;
1524
1910
  }
1525
- function filterRecords(records, clauses) {
1911
+ function fnViewDecoder(source) {
1912
+ const mf = source.moneyFields;
1913
+ if (!mf || Object.keys(mf).length === 0) return void 0;
1914
+ return (r) => decodeMoneyFields(r, mf, "raw");
1915
+ }
1916
+ function filterRecords(records, clauses, decodeForFns) {
1526
1917
  if (clauses.length === 0) return [...records];
1918
+ const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
1527
1919
  const out = [];
1528
1920
  for (const r of records) {
1921
+ const fnView = needsFnView ? decodeForFns(r) : void 0;
1529
1922
  let matches = true;
1530
1923
  for (const clause of clauses) {
1531
- if (!evaluateClause(r, clause)) {
1924
+ if (!evaluateClause(r, clause, fnView)) {
1532
1925
  matches = false;
1533
1926
  break;
1534
1927
  }
@@ -1540,10 +1933,11 @@ function filterRecords(records, clauses) {
1540
1933
  function executeClausePipeline(source, clauses, joinContext) {
1541
1934
  let rel = [...source.snapshot()];
1542
1935
  let filterBatch = [];
1936
+ const decodeForFns = fnViewDecoder(source);
1543
1937
  for (const clause of clauses) {
1544
1938
  if (clause.type === "crossJoin") {
1545
1939
  if (filterBatch.length > 0) {
1546
- rel = filterRecords(rel, filterBatch);
1940
+ rel = filterRecords(rel, filterBatch, decodeForFns);
1547
1941
  filterBatch = [];
1548
1942
  }
1549
1943
  const rightSource = joinContext.resolveSource(clause.target);
@@ -1556,7 +1950,7 @@ function executeClausePipeline(source, clauses, joinContext) {
1556
1950
  }
1557
1951
  }
1558
1952
  if (filterBatch.length > 0) {
1559
- rel = filterRecords(rel, filterBatch);
1953
+ rel = filterRecords(rel, filterBatch, decodeForFns);
1560
1954
  }
1561
1955
  return rel;
1562
1956
  }
@@ -2165,11 +2559,14 @@ var GroupedQueryN = class extends GroupedQueryBase {
2165
2559
  );
2166
2560
  }
2167
2561
  };
2168
- function groupAndReduce(records, fieldOrFields, spec) {
2562
+ function groupAndReduce(records, fieldOrFields, spec, moneyFields) {
2169
2563
  const fields = typeof fieldOrFields === "string" ? [fieldOrFields] : fieldOrFields;
2170
2564
  if (fields.length === 0) {
2171
2565
  throw new Error(".groupBy() requires at least one field");
2172
2566
  }
2567
+ if (moneyFields) {
2568
+ spec = wrapMoneyReducers(spec, moneyFields);
2569
+ }
2173
2570
  const buckets = /* @__PURE__ */ new Map();
2174
2571
  const fieldLabel = fields.length === 1 ? fields[0] : `[${fields.join(", ")}]`;
2175
2572
  for (const record of records) {
@@ -2351,7 +2748,8 @@ var ScanBuilder = class _ScanBuilder {
2351
2748
  * evaluates clauses per record in O(1) per clause.
2352
2749
  */
2353
2750
  where(field, op, value) {
2354
- const clause = { type: "field", field, op, value };
2751
+ const desc = this.moneyFields?.[field];
2752
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
2355
2753
  return new _ScanBuilder(
2356
2754
  this.pageProvider,
2357
2755
  this.pageSize,
@@ -2697,8 +3095,9 @@ var ScanBuilder = class _ScanBuilder {
2697
3095
  */
2698
3096
  recordMatches(record) {
2699
3097
  if (this.clauses.length === 0) return true;
3098
+ const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
2700
3099
  for (const clause of this.clauses) {
2701
- if (!evaluateClause(record, clause)) return false;
3100
+ if (!evaluateClause(record, clause, fnView)) return false;
2702
3101
  }
2703
3102
  return true;
2704
3103
  }