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

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 (122) hide show
  1. package/dist/aggregate/index.cjs +56 -56
  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.d.cts +5 -5
  7. package/dist/attestation/index.d.ts +5 -5
  8. package/dist/blobs/index.d.cts +6 -6
  9. package/dist/blobs/index.d.ts +6 -6
  10. package/dist/bundle/index.cjs +426 -68
  11. package/dist/bundle/index.cjs.map +1 -1
  12. package/dist/bundle/index.d.cts +7 -7
  13. package/dist/bundle/index.d.ts +7 -7
  14. package/dist/bundle/index.js +1 -1
  15. package/dist/{chunk-3EWA37FV.js → chunk-3EWXMOK3.js} +7 -266
  16. package/dist/chunk-3EWXMOK3.js.map +1 -0
  17. package/dist/{chunk-ACKFRSAH.js → chunk-7EFFHEN5.js} +26 -19
  18. package/dist/chunk-7EFFHEN5.js.map +1 -0
  19. package/dist/{chunk-UWNYBOOO.js → chunk-C5T5AFWN.js} +2 -2
  20. package/dist/chunk-CJORTUJ2.js +524 -0
  21. package/dist/chunk-CJORTUJ2.js.map +1 -0
  22. package/dist/{chunk-YNTBADIY.js → chunk-CZI2A4MQ.js} +2 -2
  23. package/dist/{chunk-KGCORI4L.js → chunk-EYVQHAGH.js} +266 -66
  24. package/dist/chunk-EYVQHAGH.js.map +1 -0
  25. package/dist/{chunk-NP6EZT44.js → chunk-IQLVUT37.js} +2 -2
  26. package/dist/{chunk-4PEFEETV.js → chunk-KIP6JLTF.js} +2 -2
  27. package/dist/{chunk-ZWTNWAO4.js → chunk-NU6Q3FOR.js} +3 -3
  28. package/dist/chunk-NU6Q3FOR.js.map +1 -0
  29. package/dist/{chunk-WIBHRONM.js → chunk-XWH4MXIU.js} +2 -2
  30. package/dist/consent/index.d.cts +6 -6
  31. package/dist/consent/index.d.ts +6 -6
  32. package/dist/derivations/index.d.cts +7 -7
  33. package/dist/derivations/index.d.ts +7 -7
  34. package/dist/{dev-unlock-DV7ujTCI.d.ts → dev-unlock-iAS8z9jc.d.ts} +1 -1
  35. package/dist/{dev-unlock-BF4OSxRv.d.cts → dev-unlock-nVkuRLLe.d.cts} +1 -1
  36. package/dist/{executor-723ZP6TH.js → executor-HSSRXDOB.js} +4 -4
  37. package/dist/guards/index.d.cts +7 -7
  38. package/dist/guards/index.d.ts +7 -7
  39. package/dist/{hash-BcF5WQXl.d.cts → hash-Cv6byZs7.d.cts} +1 -1
  40. package/dist/{hash-DswxkLtW.d.ts → hash-DHOnRarj.d.ts} +1 -1
  41. package/dist/history/index.d.cts +7 -7
  42. package/dist/history/index.d.ts +7 -7
  43. package/dist/i18n/index.d.cts +6 -6
  44. package/dist/i18n/index.d.ts +6 -6
  45. package/dist/{immutable-guard-C8IYdzfu.d.ts → immutable-guard-BehB1YGB.d.ts} +1 -1
  46. package/dist/{immutable-guard-7KqslW2K.d.cts → immutable-guard-yBEOYmif.d.cts} +1 -1
  47. package/dist/{index-CUVOMtgg.d.cts → index-D95VK1Qy.d.cts} +11 -3
  48. package/dist/{index-Cqzp4tt9.d.ts → index-XNB2r6bX.d.ts} +11 -3
  49. package/dist/index.cjs +567 -70
  50. package/dist/index.cjs.map +1 -1
  51. package/dist/index.d.cts +145 -15
  52. package/dist/index.d.ts +145 -15
  53. package/dist/index.js +132 -16
  54. package/dist/index.js.map +1 -1
  55. package/dist/indexing/index.cjs +92 -31
  56. package/dist/indexing/index.cjs.map +1 -1
  57. package/dist/indexing/index.d.cts +3 -3
  58. package/dist/indexing/index.d.ts +3 -3
  59. package/dist/indexing/index.js +3 -3
  60. package/dist/{lazy-builder-D5GU14TS.d.ts → lazy-builder-ChSqcF5t.d.ts} +1 -1
  61. package/dist/{lazy-builder-Ci5_YG73.d.cts → lazy-builder-eYZzLEL1.d.cts} +1 -1
  62. package/dist/materialized-views/index.cjs +2 -2
  63. package/dist/materialized-views/index.cjs.map +1 -1
  64. package/dist/materialized-views/index.d.cts +7 -7
  65. package/dist/materialized-views/index.d.ts +7 -7
  66. package/dist/materialized-views/index.js +5 -5
  67. package/dist/{noydb-VZ4JVW55.js → noydb-GZGFBA4E.js} +8 -8
  68. package/dist/overlay-views/index.d.cts +7 -7
  69. package/dist/overlay-views/index.d.ts +7 -7
  70. package/dist/periods/index.d.cts +6 -6
  71. package/dist/periods/index.d.ts +6 -6
  72. package/dist/{predicate-Bt5ft-9c.d.cts → predicate-BmhBSPCH.d.cts} +59 -2
  73. package/dist/{predicate-Bt5ft-9c.d.ts → predicate-BmhBSPCH.d.ts} +59 -2
  74. package/dist/query/index.cjs +580 -195
  75. package/dist/query/index.cjs.map +1 -1
  76. package/dist/query/index.d.cts +3 -3
  77. package/dist/query/index.d.ts +3 -3
  78. package/dist/query/index.js +5 -5
  79. package/dist/session/index.d.cts +7 -7
  80. package/dist/session/index.d.ts +7 -7
  81. package/dist/shadow/index.d.cts +6 -6
  82. package/dist/shadow/index.d.ts +6 -6
  83. package/dist/snapshots/index.d.cts +6 -6
  84. package/dist/snapshots/index.d.ts +6 -6
  85. package/dist/{stale-7FRJVHN6.js → stale-JH67FU57.js} +2 -2
  86. package/dist/store/index.d.cts +6 -6
  87. package/dist/store/index.d.ts +6 -6
  88. package/dist/{strategy-CrS7PnbE.d.ts → strategy-CbneC7bS.d.cts} +1 -1
  89. package/dist/{strategy-CrS7PnbE.d.cts → strategy-CbneC7bS.d.ts} +1 -1
  90. package/dist/sync/index.d.cts +5 -5
  91. package/dist/sync/index.d.ts +5 -5
  92. package/dist/team/index.d.cts +6 -6
  93. package/dist/team/index.d.ts +6 -6
  94. package/dist/tx/index.d.cts +6 -6
  95. package/dist/tx/index.d.ts +6 -6
  96. package/dist/{types-BFHQUjdy.d.ts → types-4t1-tWS4.d.ts} +22 -7
  97. package/dist/{types-V5R2-pd4.d.cts → types-BpPV5uyy.d.cts} +22 -7
  98. package/dist/{ulid-CwNf9e6-.d.cts → ulid-CiPrpGqm.d.cts} +1 -1
  99. package/dist/{ulid-p2nKiiKg.d.ts → ulid-DAfenvFd.d.ts} +1 -1
  100. package/dist/{vault-group-W7QC4UYW.js → vault-group-KOM7QRJG.js} +3 -3
  101. package/dist/{with-derivation-C9K43BOB.d.cts → with-derivation-DBqJB3dQ.d.cts} +1 -1
  102. package/dist/{with-derivation-Ds9yZgCj.d.ts → with-derivation-OK9M2sJE.d.ts} +1 -1
  103. package/dist/{with-materialized-view-DwR4jkV5.d.ts → with-materialized-view-Dt-ufPWQ.d.ts} +1 -1
  104. package/dist/{with-materialized-view-DgQcAjYv.d.cts → with-materialized-view-NzuxYPDF.d.cts} +1 -1
  105. package/dist/{with-overlayed-view-7-rUB3vD.d.cts → with-overlayed-view-CC0_ocy-.d.cts} +1 -1
  106. package/dist/{with-overlayed-view-ByyhHdVr.d.ts → with-overlayed-view-eDvMs6LO.d.ts} +1 -1
  107. package/package.json +3 -3
  108. package/dist/chunk-3EWA37FV.js.map +0 -1
  109. package/dist/chunk-ACKFRSAH.js.map +0 -1
  110. package/dist/chunk-KGCORI4L.js.map +0 -1
  111. package/dist/chunk-TV3YZ35S.js +0 -90
  112. package/dist/chunk-TV3YZ35S.js.map +0 -1
  113. package/dist/chunk-ZWTNWAO4.js.map +0 -1
  114. /package/dist/{chunk-UWNYBOOO.js.map → chunk-C5T5AFWN.js.map} +0 -0
  115. /package/dist/{chunk-YNTBADIY.js.map → chunk-CZI2A4MQ.js.map} +0 -0
  116. /package/dist/{chunk-NP6EZT44.js.map → chunk-IQLVUT37.js.map} +0 -0
  117. /package/dist/{chunk-4PEFEETV.js.map → chunk-KIP6JLTF.js.map} +0 -0
  118. /package/dist/{chunk-WIBHRONM.js.map → chunk-XWH4MXIU.js.map} +0 -0
  119. /package/dist/{executor-723ZP6TH.js.map → executor-HSSRXDOB.js.map} +0 -0
  120. /package/dist/{noydb-VZ4JVW55.js.map → noydb-GZGFBA4E.js.map} +0 -0
  121. /package/dist/{stale-7FRJVHN6.js.map → stale-JH67FU57.js.map} +0 -0
  122. /package/dist/{vault-group-W7QC4UYW.js.map → vault-group-KOM7QRJG.js.map} +0 -0
@@ -3894,6 +3894,115 @@ var init_descriptor = __esm({
3894
3894
  }
3895
3895
  });
3896
3896
 
3897
+ // src/money/paths.ts
3898
+ function parseMoneyPath(path) {
3899
+ const cached = parseCache.get(path);
3900
+ if (cached) return cached;
3901
+ if (typeof path !== "string" || path.length === 0) {
3902
+ throw new ValidationError("moneyFields: path must be a non-empty string");
3903
+ }
3904
+ const segments = [];
3905
+ for (const part of path.split(".")) {
3906
+ const m = SEGMENT_RE.exec(part);
3907
+ if (!m) {
3908
+ throw new ValidationError(
3909
+ `moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
3910
+ );
3911
+ }
3912
+ const array = m[2] === "[]";
3913
+ segments.push(
3914
+ m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
3915
+ );
3916
+ }
3917
+ parseCache.set(path, segments);
3918
+ return segments;
3919
+ }
3920
+ function isSimpleMoneyPath(path) {
3921
+ return !path.includes(".") && !path.includes("[") && !path.includes("*");
3922
+ }
3923
+ function validateMoneyFieldPaths(moneyFields) {
3924
+ for (const path of Object.keys(moneyFields)) parseMoneyPath(path);
3925
+ }
3926
+ function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
3927
+ if (node === null || node === void 0) return node;
3928
+ const seg = segments[index];
3929
+ const last = index === segments.length - 1;
3930
+ if (seg.kind === "key") {
3931
+ if (typeof node !== "object" || Array.isArray(node)) {
3932
+ if (lenient) return node;
3933
+ throw new ValidationError(
3934
+ `moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
3935
+ );
3936
+ }
3937
+ const obj2 = node;
3938
+ if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
3939
+ if (seg.array) {
3940
+ const arr = obj2[seg.key];
3941
+ if (!Array.isArray(arr)) {
3942
+ if (lenient) return node;
3943
+ throw new ValidationError(
3944
+ `moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
3945
+ );
3946
+ }
3947
+ const cloned = [...arr];
3948
+ if (last) {
3949
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
3950
+ } else {
3951
+ for (let i = 0; i < cloned.length; i++) {
3952
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
3953
+ }
3954
+ }
3955
+ return { ...obj2, [seg.key]: cloned };
3956
+ }
3957
+ const clone3 = { ...obj2 };
3958
+ if (last) {
3959
+ visit(clone3, seg.key);
3960
+ } else {
3961
+ clone3[seg.key] = transformAtMoneyPath(clone3[seg.key], path, segments, index + 1, visit, lenient);
3962
+ }
3963
+ return clone3;
3964
+ }
3965
+ if (seg.array) {
3966
+ if (!Array.isArray(node)) {
3967
+ if (lenient) return node;
3968
+ throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
3969
+ }
3970
+ const cloned = [...node];
3971
+ if (last) {
3972
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
3973
+ } else {
3974
+ for (let i = 0; i < cloned.length; i++) {
3975
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
3976
+ }
3977
+ }
3978
+ return cloned;
3979
+ }
3980
+ if (typeof node !== "object" || Array.isArray(node)) {
3981
+ if (lenient) return node;
3982
+ throw new ValidationError(
3983
+ `moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
3984
+ );
3985
+ }
3986
+ const obj = node;
3987
+ const clone2 = { ...obj };
3988
+ for (const key of Object.keys(obj)) {
3989
+ const v = clone2[key];
3990
+ if (v === null || v === void 0) continue;
3991
+ if (last) visit(clone2, key);
3992
+ else clone2[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
3993
+ }
3994
+ return clone2;
3995
+ }
3996
+ var SEGMENT_RE, parseCache;
3997
+ var init_paths = __esm({
3998
+ "src/money/paths.ts"() {
3999
+ "use strict";
4000
+ init_errors();
4001
+ SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
4002
+ parseCache = /* @__PURE__ */ new Map();
4003
+ }
4004
+ });
4005
+
3897
4006
  // src/money/normalize.ts
3898
4007
  function isMoneyValueObject(v) {
3899
4008
  return typeof v === "object" && v !== null && "currency" in v;
@@ -3906,33 +4015,68 @@ function quantizeAmount(field, input, scale, rounding) {
3906
4015
  }
3907
4016
  return r.value.toString();
3908
4017
  }
4018
+ function canonicalizeStoredMoney(record, moneyFields) {
4019
+ if (record === null || record === void 0) return record;
4020
+ if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
4021
+ return decodeMoneyFields(record, moneyFields, "raw");
4022
+ }
4023
+ function canonicalizeIncomingMoney(record, moneyFields) {
4024
+ if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
4025
+ try {
4026
+ return decodeMoneyFields(
4027
+ quantizeMoneyFields(record, moneyFields),
4028
+ moneyFields,
4029
+ "raw"
4030
+ );
4031
+ } catch {
4032
+ return record;
4033
+ }
4034
+ }
4035
+ function quantizeValue(field, raw, desc) {
4036
+ if (desc.mode === "fixed") {
4037
+ const currency2 = desc.fixedCurrency;
4038
+ return quantizeAmount(field, raw, desc.scaleFor(currency2), desc.rounding);
4039
+ }
4040
+ let amount;
4041
+ let currency;
4042
+ if (isMoneyValueObject(raw)) {
4043
+ currency = String(raw.currency);
4044
+ amount = raw.amount;
4045
+ } else {
4046
+ const sole = desc.soleCurrency();
4047
+ if (sole === void 0) {
4048
+ throw new TypeError(
4049
+ `money: field "${field}" is multi-currency \u2014 write { amount, currency }, not a bare amount`
4050
+ );
4051
+ }
4052
+ currency = sole;
4053
+ amount = raw;
4054
+ }
4055
+ const scale = desc.scaleFor(currency);
4056
+ return { amount: quantizeAmount(field, amount, scale, desc.rounding), currency };
4057
+ }
3909
4058
  function quantizeMoneyFields(record, moneyFields) {
3910
- const out = { ...record };
3911
- for (const [field, desc] of Object.entries(moneyFields)) {
3912
- const raw = out[field];
3913
- if (raw === null || raw === void 0) continue;
3914
- if (desc.mode === "fixed") {
3915
- const currency2 = desc.fixedCurrency;
3916
- out[field] = quantizeAmount(field, raw, desc.scaleFor(currency2), desc.rounding);
4059
+ let out = { ...record };
4060
+ for (const [path, desc] of Object.entries(moneyFields)) {
4061
+ if (isSimpleMoneyPath(path)) {
4062
+ const raw = out[path];
4063
+ if (raw === null || raw === void 0) continue;
4064
+ out[path] = quantizeValue(path, raw, desc);
3917
4065
  continue;
3918
4066
  }
3919
- let amount;
3920
- let currency;
3921
- if (isMoneyValueObject(raw)) {
3922
- currency = String(raw.currency);
3923
- amount = raw.amount;
3924
- } else {
3925
- const sole = desc.soleCurrency();
3926
- if (sole === void 0) {
3927
- throw new TypeError(
3928
- `money: field "${field}" is multi-currency \u2014 write { amount, currency }, not a bare amount`
3929
- );
3930
- }
3931
- currency = sole;
3932
- amount = raw;
3933
- }
3934
- const scale = desc.scaleFor(currency);
3935
- out[field] = { amount: quantizeAmount(field, amount, scale, desc.rounding), currency };
4067
+ out = transformAtMoneyPath(
4068
+ out,
4069
+ path,
4070
+ parseMoneyPath(path),
4071
+ 0,
4072
+ (container, key) => {
4073
+ const raw = container[key];
4074
+ if (raw === null || raw === void 0) return;
4075
+ container[key] = quantizeValue(path, raw, desc);
4076
+ },
4077
+ /* lenient */
4078
+ false
4079
+ );
3936
4080
  }
3937
4081
  return out;
3938
4082
  }
@@ -3945,33 +4089,70 @@ function formatCurrency(decimal, currency, scale, locale) {
3945
4089
  });
3946
4090
  return fmt.format(decimal);
3947
4091
  }
4092
+ function decodeValue(stored, desc) {
4093
+ let currency;
4094
+ let scaledIntString;
4095
+ if (desc.mode === "fixed") {
4096
+ if (typeof stored !== "string" && typeof stored !== "number") return null;
4097
+ currency = desc.fixedCurrency;
4098
+ scaledIntString = String(stored);
4099
+ } else {
4100
+ if (!isMoneyValueObject(stored)) return null;
4101
+ const amount = stored.amount;
4102
+ if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
4103
+ currency = stored.currency;
4104
+ scaledIntString = String(amount);
4105
+ }
4106
+ const scale = desc.scaleFor(currency);
4107
+ let decimal;
4108
+ try {
4109
+ decimal = formatScaledInt(BigInt(scaledIntString), scale);
4110
+ } catch {
4111
+ return null;
4112
+ }
4113
+ return {
4114
+ decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
4115
+ decimal,
4116
+ currency,
4117
+ scale
4118
+ };
4119
+ }
3948
4120
  function decodeMoneyFields(record, moneyFields, locale) {
3949
- const out = { ...record };
4121
+ let out = { ...record };
3950
4122
  const format = locale !== "raw";
3951
4123
  const fmtLocale = typeof locale === "string" && locale !== "raw" ? locale : "en-US";
3952
- for (const [field, desc] of Object.entries(moneyFields)) {
3953
- const stored = out[field];
3954
- if (stored === null || stored === void 0) continue;
3955
- let currency;
3956
- let scaledIntString;
3957
- if (desc.mode === "fixed") {
3958
- if (typeof stored !== "string" && typeof stored !== "number") continue;
3959
- currency = desc.fixedCurrency;
3960
- scaledIntString = String(stored);
3961
- } else {
3962
- if (!isMoneyValueObject(stored)) continue;
3963
- const amount = stored.amount;
3964
- if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") continue;
3965
- currency = stored.currency;
3966
- scaledIntString = String(amount);
3967
- }
3968
- const scale = desc.scaleFor(currency);
3969
- const decimal = formatScaledInt(BigInt(scaledIntString), scale);
3970
- out[field] = desc.mode === "fixed" ? decimal : { amount: decimal, currency };
3971
- if (format) {
3972
- out[`${field}Formatted`] = formatCurrency(decimal, currency, scale, fmtLocale);
3973
- out[`${field}Number`] = Number(decimal);
4124
+ for (const [path, desc] of Object.entries(moneyFields)) {
4125
+ if (isSimpleMoneyPath(path)) {
4126
+ const stored = out[path];
4127
+ if (stored === null || stored === void 0) continue;
4128
+ const r = decodeValue(stored, desc);
4129
+ if (r === null) continue;
4130
+ out[path] = r.decoded;
4131
+ if (format) {
4132
+ out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
4133
+ out[`${path}Number`] = Number(r.decimal);
4134
+ }
4135
+ continue;
3974
4136
  }
4137
+ out = transformAtMoneyPath(
4138
+ out,
4139
+ path,
4140
+ parseMoneyPath(path),
4141
+ 0,
4142
+ (container, key) => {
4143
+ const stored = container[key];
4144
+ if (stored === null || stored === void 0) return;
4145
+ const r = decodeValue(stored, desc);
4146
+ if (r === null) return;
4147
+ container[key] = r.decoded;
4148
+ if (format && typeof key === "string" && !Array.isArray(container)) {
4149
+ container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
4150
+ container[`${key}Number`] = Number(r.decimal);
4151
+ }
4152
+ },
4153
+ /* lenient */
4154
+ true
4155
+ );
3975
4156
  }
3976
4157
  return out;
3977
4158
  }
@@ -3980,6 +4161,7 @@ var init_normalize = __esm({
3980
4161
  "use strict";
3981
4162
  init_fixed_point();
3982
4163
  init_descriptor();
4164
+ init_paths();
3983
4165
  }
3984
4166
  });
3985
4167
 
@@ -4152,6 +4334,146 @@ var init_strategy3 = __esm({
4152
4334
  }
4153
4335
  });
4154
4336
 
4337
+ // src/money/where.ts
4338
+ function isMoneyValueObject2(v) {
4339
+ return typeof v === "object" && v !== null && "currency" in v;
4340
+ }
4341
+ function parseOperand(field, raw, desc) {
4342
+ let amount;
4343
+ let currency;
4344
+ if (desc.mode === "fixed") {
4345
+ currency = desc.fixedCurrency;
4346
+ amount = raw;
4347
+ } else if (isMoneyValueObject2(raw)) {
4348
+ currency = String(raw.currency);
4349
+ amount = raw.amount;
4350
+ } else {
4351
+ const sole = desc.soleCurrency();
4352
+ if (sole === void 0) {
4353
+ throw new MoneyUnsupportedError(
4354
+ `where("${field}"): field is multi-currency \u2014 compare against { amount, currency }, not a bare amount`
4355
+ );
4356
+ }
4357
+ currency = sole;
4358
+ amount = raw;
4359
+ }
4360
+ if (typeof amount !== "number" && typeof amount !== "string") {
4361
+ throw new MoneyUnsupportedError(
4362
+ `where("${field}"): operand ${JSON.stringify(raw)} is not a money amount`
4363
+ );
4364
+ }
4365
+ const r = parseToScaledInt(amount, desc.scaleFor(currency), desc.rounding);
4366
+ if (!r.ok) {
4367
+ throw new MoneyUnsupportedError(
4368
+ `where("${field}"): operand ${JSON.stringify(amount)} is not a finite decimal`
4369
+ );
4370
+ }
4371
+ return { scaled: r.value.toString(), currency };
4372
+ }
4373
+ function moneyFieldClause(field, op, value, desc) {
4374
+ switch (op) {
4375
+ case "==":
4376
+ case "!=":
4377
+ case "<":
4378
+ case "<=":
4379
+ case ">":
4380
+ case ">=": {
4381
+ const e = parseOperand(field, value, desc);
4382
+ return withMoney(field, op, value, desc, [e]);
4383
+ }
4384
+ case "between": {
4385
+ if (!Array.isArray(value) || value.length !== 2) {
4386
+ throw new MoneyUnsupportedError(`where("${field}"): 'between' needs a [lo, hi] tuple`);
4387
+ }
4388
+ const lo = parseOperand(field, value[0], desc);
4389
+ const hi = parseOperand(field, value[1], desc);
4390
+ if (lo.currency !== hi.currency) {
4391
+ throw new MoneyUnsupportedError(
4392
+ `where("${field}"): 'between' bounds mix currencies (${lo.currency} vs ${hi.currency})`
4393
+ );
4394
+ }
4395
+ return withMoney(field, op, value, desc, [lo, hi]);
4396
+ }
4397
+ case "in": {
4398
+ if (!Array.isArray(value)) {
4399
+ throw new MoneyUnsupportedError(`where("${field}"): 'in' needs an array of amounts`);
4400
+ }
4401
+ return withMoney(field, op, value, desc, value.map((v) => parseOperand(field, v, desc)));
4402
+ }
4403
+ default:
4404
+ throw new MoneyUnsupportedError(
4405
+ `where("${field}"): operator '${op}' is not supported on a money field`
4406
+ );
4407
+ }
4408
+ }
4409
+ function withMoney(field, op, originalValue, desc, entries) {
4410
+ const money = { mode: desc.mode, entries };
4411
+ const value = desc.mode !== "fixed" ? originalValue : entries.length === 1 && op !== "in" && op !== "between" ? entries[0].scaled : entries.map((e) => e.scaled);
4412
+ return { type: "field", field, op, value, money };
4413
+ }
4414
+ function readStored(actual, operand) {
4415
+ let amount;
4416
+ let currency;
4417
+ if (operand.mode === "fixed") {
4418
+ if (typeof actual !== "string" && typeof actual !== "number") return null;
4419
+ amount = actual;
4420
+ currency = operand.entries[0]?.currency ?? "";
4421
+ } else {
4422
+ if (!isMoneyValueObject2(actual)) return null;
4423
+ if (typeof actual.currency !== "string") return null;
4424
+ amount = actual.amount;
4425
+ currency = actual.currency;
4426
+ }
4427
+ if (typeof amount !== "string" && typeof amount !== "number") return null;
4428
+ try {
4429
+ return { scaled: BigInt(amount).toString(), currency };
4430
+ } catch {
4431
+ return null;
4432
+ }
4433
+ }
4434
+ function evaluateMoneyClause(actual, op, operand) {
4435
+ const stored = readStored(actual, operand);
4436
+ if (stored === null) return op === "!=";
4437
+ const a = BigInt(stored.scaled);
4438
+ if (op === "in") {
4439
+ return operand.entries.some(
4440
+ (e2) => e2.currency === stored.currency && BigInt(e2.scaled) === a
4441
+ );
4442
+ }
4443
+ if (op === "between") {
4444
+ const [lo, hi] = operand.entries;
4445
+ if (!lo || !hi || lo.currency !== stored.currency) return false;
4446
+ return a >= BigInt(lo.scaled) && a <= BigInt(hi.scaled);
4447
+ }
4448
+ const e = operand.entries[0];
4449
+ if (!e) return false;
4450
+ if (e.currency !== stored.currency) return op === "!=";
4451
+ const b = BigInt(e.scaled);
4452
+ switch (op) {
4453
+ case "==":
4454
+ return a === b;
4455
+ case "!=":
4456
+ return a !== b;
4457
+ case "<":
4458
+ return a < b;
4459
+ case "<=":
4460
+ return a <= b;
4461
+ case ">":
4462
+ return a > b;
4463
+ case ">=":
4464
+ return a >= b;
4465
+ default:
4466
+ return false;
4467
+ }
4468
+ }
4469
+ var init_where = __esm({
4470
+ "src/money/where.ts"() {
4471
+ "use strict";
4472
+ init_fixed_point();
4473
+ init_descriptor();
4474
+ }
4475
+ });
4476
+
4155
4477
  // src/query/predicate.ts
4156
4478
  function readPath(record, path) {
4157
4479
  if (record === null || record === void 0) return void 0;
@@ -4169,6 +4491,7 @@ function readPath(record, path) {
4169
4491
  function evaluateFieldClause(record, clause) {
4170
4492
  const actual = readPath(record, clause.field);
4171
4493
  const { op, value } = clause;
4494
+ if (clause.money) return evaluateMoneyClause(actual, op, clause.money);
4172
4495
  switch (op) {
4173
4496
  case "==":
4174
4497
  return actual === value;
@@ -4209,14 +4532,14 @@ function isComparable(a, b) {
4209
4532
  if (a instanceof Date && b instanceof Date) return true;
4210
4533
  return false;
4211
4534
  }
4212
- function evaluateClause(record, clause) {
4535
+ function evaluateClause(record, clause, fnRecord) {
4213
4536
  switch (clause.type) {
4214
4537
  case "field":
4215
4538
  return evaluateFieldClause(record, clause);
4216
4539
  case "filter":
4217
- return clause.fn(record);
4540
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record);
4218
4541
  case "wherePredicate":
4219
- return clause.fn(record, clause.ctx);
4542
+ return clause.fn(fnRecord !== void 0 ? fnRecord : record, clause.ctx);
4220
4543
  case "crossJoin":
4221
4544
  throw new Error(
4222
4545
  `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.`
@@ -4224,20 +4547,28 @@ function evaluateClause(record, clause) {
4224
4547
  case "group":
4225
4548
  if (clause.op === "and") {
4226
4549
  for (const child of clause.clauses) {
4227
- if (!evaluateClause(record, child)) return false;
4550
+ if (!evaluateClause(record, child, fnRecord)) return false;
4228
4551
  }
4229
4552
  return true;
4230
4553
  } else {
4231
4554
  for (const child of clause.clauses) {
4232
- if (evaluateClause(record, child)) return true;
4555
+ if (evaluateClause(record, child, fnRecord)) return true;
4233
4556
  }
4234
4557
  return false;
4235
4558
  }
4236
4559
  }
4237
4560
  }
4561
+ function hasFnClause(clauses) {
4562
+ for (const c of clauses) {
4563
+ if (c.type === "filter" || c.type === "wherePredicate") return true;
4564
+ if (c.type === "group" && hasFnClause(c.clauses)) return true;
4565
+ }
4566
+ return false;
4567
+ }
4238
4568
  var init_predicate = __esm({
4239
4569
  "src/query/predicate.ts"() {
4240
4570
  "use strict";
4571
+ init_where();
4241
4572
  }
4242
4573
  });
4243
4574
 
@@ -4698,7 +5029,7 @@ function executePlanWithSource(source, plan, joinContext) {
4698
5029
  result = executeClausePipeline(source, plan.clauses, joinContext);
4699
5030
  } else {
4700
5031
  const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
4701
- result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses);
5032
+ result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
4702
5033
  }
4703
5034
  if (plan.orderBy.length > 0) {
4704
5035
  result = sortRecords(result, plan.orderBy);
@@ -4721,6 +5052,7 @@ function candidateRecords(source, clauses) {
4721
5052
  const clause = clauses[i];
4722
5053
  if (clause.type !== "field") continue;
4723
5054
  if (!indexes.has(clause.field)) continue;
5055
+ if (clause.money?.mode === "multi") continue;
4724
5056
  let ids = null;
4725
5057
  if (clause.op === "==") {
4726
5058
  ids = indexes.lookupEqual(clause.field, clause.value);
@@ -4748,13 +5080,20 @@ function materializeIds(ids, lookupById) {
4748
5080
  }
4749
5081
  return out;
4750
5082
  }
4751
- function filterRecords(records, clauses) {
5083
+ function fnViewDecoder(source) {
5084
+ const mf = source.moneyFields;
5085
+ if (!mf || Object.keys(mf).length === 0) return void 0;
5086
+ return (r) => decodeMoneyFields(r, mf, "raw");
5087
+ }
5088
+ function filterRecords(records, clauses, decodeForFns) {
4752
5089
  if (clauses.length === 0) return [...records];
5090
+ const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
4753
5091
  const out = [];
4754
5092
  for (const r of records) {
5093
+ const fnView = needsFnView ? decodeForFns(r) : void 0;
4755
5094
  let matches = true;
4756
5095
  for (const clause of clauses) {
4757
- if (!evaluateClause(r, clause)) {
5096
+ if (!evaluateClause(r, clause, fnView)) {
4758
5097
  matches = false;
4759
5098
  break;
4760
5099
  }
@@ -4766,10 +5105,11 @@ function filterRecords(records, clauses) {
4766
5105
  function executeClausePipeline(source, clauses, joinContext) {
4767
5106
  let rel = [...source.snapshot()];
4768
5107
  let filterBatch = [];
5108
+ const decodeForFns = fnViewDecoder(source);
4769
5109
  for (const clause of clauses) {
4770
5110
  if (clause.type === "crossJoin") {
4771
5111
  if (filterBatch.length > 0) {
4772
- rel = filterRecords(rel, filterBatch);
5112
+ rel = filterRecords(rel, filterBatch, decodeForFns);
4773
5113
  filterBatch = [];
4774
5114
  }
4775
5115
  const rightSource = joinContext.resolveSource(clause.target);
@@ -4782,7 +5122,7 @@ function executeClausePipeline(source, clauses, joinContext) {
4782
5122
  }
4783
5123
  }
4784
5124
  if (filterBatch.length > 0) {
4785
- rel = filterRecords(rel, filterBatch);
5125
+ rel = filterRecords(rel, filterBatch, decodeForFns);
4786
5126
  }
4787
5127
  return rel;
4788
5128
  }
@@ -4963,6 +5303,7 @@ var init_builder = __esm({
4963
5303
  init_strategy4();
4964
5304
  init_money_reducer();
4965
5305
  init_normalize();
5306
+ init_where();
4966
5307
  EMPTY_PLAN = {
4967
5308
  clauses: [],
4968
5309
  orderBy: [],
@@ -5054,9 +5395,18 @@ var init_builder = __esm({
5054
5395
  this.predicates
5055
5396
  );
5056
5397
  }
5057
- /** Add a field comparison. Multiple where() calls are AND-combined. */
5398
+ /**
5399
+ * Add a field comparison. Multiple where() calls are AND-combined.
5400
+ *
5401
+ * A declared money field compares in MAJOR units (#336): the operand
5402
+ * (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
5403
+ * quantized into stored scaled-int space at build time and evaluated
5404
+ * BigInt-exact per record. A malformed operand or a string operator
5405
+ * (`contains`/`startsWith`) throws here, at the call site.
5406
+ */
5058
5407
  where(field, op, value) {
5059
- const clause = { type: "field", field, op, value };
5408
+ const desc = this.source.moneyFields?.[field];
5409
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
5060
5410
  return new _Query(
5061
5411
  this.source,
5062
5412
  { ...this.plan, clauses: [...this.plan.clauses, clause] },
@@ -5382,7 +5732,7 @@ var init_builder = __esm({
5382
5732
  }
5383
5733
  const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
5384
5734
  if (remainingClauses.length === 0) return candidates.length;
5385
- return filterRecords(candidates, remainingClauses).length;
5735
+ return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
5386
5736
  }
5387
5737
  /**
5388
5738
  * Reduce the matching records through a named set of reducers.
@@ -5439,7 +5789,7 @@ var init_builder = __esm({
5439
5789
  return executeClausePipeline(source, clauses, joinCtx);
5440
5790
  }
5441
5791
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
5442
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
5792
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
5443
5793
  };
5444
5794
  const upstreams = [];
5445
5795
  if (source.subscribe) {
@@ -5462,7 +5812,7 @@ var init_builder = __esm({
5462
5812
  return executeClausePipeline(source, clauses, joinCtx);
5463
5813
  }
5464
5814
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
5465
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
5815
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
5466
5816
  };
5467
5817
  const upstreams = [];
5468
5818
  if (source.subscribe) {
@@ -5735,6 +6085,7 @@ var init_scan_builder = __esm({
5735
6085
  init_predicate();
5736
6086
  init_errors();
5737
6087
  init_normalize();
6088
+ init_where();
5738
6089
  DEFAULT_SCAN_PAGE_SIZE = 100;
5739
6090
  ScanBuilder = class _ScanBuilder {
5740
6091
  pageProvider;
@@ -5798,7 +6149,8 @@ var init_scan_builder = __esm({
5798
6149
  * evaluates clauses per record in O(1) per clause.
5799
6150
  */
5800
6151
  where(field, op, value) {
5801
- const clause = { type: "field", field, op, value };
6152
+ const desc = this.moneyFields?.[field];
6153
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
5802
6154
  return new _ScanBuilder(
5803
6155
  this.pageProvider,
5804
6156
  this.pageSize,
@@ -6144,8 +6496,9 @@ var init_scan_builder = __esm({
6144
6496
  */
6145
6497
  recordMatches(record) {
6146
6498
  if (this.clauses.length === 0) return true;
6499
+ const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
6147
6500
  for (const clause of this.clauses) {
6148
- if (!evaluateClause(record, clause)) return false;
6501
+ if (!evaluateClause(record, clause, fnView)) return false;
6149
6502
  }
6150
6503
  return true;
6151
6504
  }
@@ -7686,6 +8039,7 @@ var init_collection = __esm({
7686
8039
  init_strategy();
7687
8040
  init_core();
7688
8041
  init_normalize();
8042
+ init_paths();
7689
8043
  init_computed();
7690
8044
  init_strategy2();
7691
8045
  init_policy();
@@ -8008,6 +8362,7 @@ var init_collection = __esm({
8008
8362
  this.joinResolver = opts.joinResolver;
8009
8363
  this.i18nFields = opts.i18nFields;
8010
8364
  this.dictKeyFields = opts.dictKeyFields;
8365
+ if (opts.moneyFields) validateMoneyFieldPaths(opts.moneyFields);
8011
8366
  this.moneyFields = opts.moneyFields;
8012
8367
  this.computed = opts.computed;
8013
8368
  this.dictLabelResolver = opts.dictLabelResolver;
@@ -8141,7 +8496,9 @@ var init_collection = __esm({
8141
8496
  * declaration; this reconciles that ordering. First-wins. Not public.
8142
8497
  */
8143
8498
  _applyMoneyFields(moneyFields) {
8144
- if (this.moneyFields === void 0) this.moneyFields = moneyFields;
8499
+ if (this.moneyFields !== void 0) return;
8500
+ validateMoneyFieldPaths(moneyFields);
8501
+ this.moneyFields = moneyFields;
8145
8502
  }
8146
8503
  /** @internal — attach computed fields post-construction. See {@link _applyMoneyFields}. */
8147
8504
  _applyComputed(computed) {
@@ -8308,6 +8665,7 @@ var init_collection = __esm({
8308
8665
  if (!hasWritePermission(this.keyring, this.name)) {
8309
8666
  throw new ReadOnlyError();
8310
8667
  }
8668
+ record = canonicalizeIncomingMoney(record, this.moneyFields);
8311
8669
  if (this.subsystemBus?.hasGateHandlers("beforePut")) {
8312
8670
  const existingEnv = await this.adapter.get(this.vault, this.name, id);
8313
8671
  let existingRecord = null;
@@ -8324,7 +8682,7 @@ var init_collection = __esm({
8324
8682
  collection: this.name,
8325
8683
  docId: id,
8326
8684
  incoming: record,
8327
- existing: existingRecord,
8685
+ existing: canonicalizeStoredMoney(existingRecord, this.moneyFields),
8328
8686
  existingVersion: existingEnv?._v ?? 0,
8329
8687
  existingTs: existingEnv?._ts,
8330
8688
  userId: this.keyring.userId,
@@ -8593,7 +8951,7 @@ var init_collection = __esm({
8593
8951
  */
8594
8952
  async dispatchDerivations(id, record, version) {
8595
8953
  if (this.derivationSource === void 0) return;
8596
- const incoming = record;
8954
+ const incoming = canonicalizeStoredMoney(record, this.moneyFields);
8597
8955
  if (incoming && typeof incoming === "object" && "_derivedFrom" in incoming) return;
8598
8956
  const registry = this.derivationSource.registry();
8599
8957
  const strategies = registry.strategiesForSource(this.name);
@@ -8826,7 +9184,7 @@ var init_collection = __esm({
8826
9184
  vault: this.vault,
8827
9185
  collection: this.name,
8828
9186
  docId: id,
8829
- existing: existingRecord,
9187
+ existing: canonicalizeStoredMoney(existingRecord, this.moneyFields),
8830
9188
  existingVersion: existingEnv._v,
8831
9189
  existingTs: existingEnv._ts,
8832
9190
  internal,