@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
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,20 +2853,28 @@ 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
 
@@ -5577,9 +5750,11 @@ __export(src_exports, {
5577
5750
  WeakPassphraseError: () => WeakPassphraseError,
5578
5751
  activeSessionCount: () => activeSessionCount,
5579
5752
  additiveOnly: () => additiveOnly,
5753
+ allocate: () => allocate,
5580
5754
  applyI18nLocale: () => applyI18nLocale,
5581
5755
  applyJoins: () => applyJoins,
5582
5756
  applyPatch: () => applyPatch,
5757
+ asMoney: () => asMoney,
5583
5758
  assertStrongPassphrase: () => assertStrongPassphrase,
5584
5759
  assertTierAccess: () => assertTierAccess,
5585
5760
  avg: () => avg,
@@ -5656,6 +5831,7 @@ __export(src_exports, {
5656
5831
  isI18nTextDescriptor: () => isI18nTextDescriptor,
5657
5832
  isMagicLinkGrantExpired: () => isMagicLinkGrantExpired,
5658
5833
  isMoneyDescriptor: () => isMoneyDescriptor,
5834
+ isMoneyString: () => isMoneyString,
5659
5835
  isPreCompressed: () => isPreCompressed,
5660
5836
  isPublicEnvelope: () => isPublicEnvelope,
5661
5837
  isSessionAlive: () => isSessionAlive,
@@ -5688,6 +5864,8 @@ __export(src_exports, {
5688
5864
  mintShamirRecoveryEntry: () => mintShamirRecoveryEntry,
5689
5865
  mintWrappedDeksBlob: () => mintWrappedDeksBlob,
5690
5866
  money: () => money,
5867
+ moneyNumber: () => moneyNumber,
5868
+ mulRate: () => mulRate,
5691
5869
  paddedIndex: () => paddedIndex,
5692
5870
  parseBytes: () => parseBytes,
5693
5871
  parseIndex: () => parseIndex,
@@ -11579,6 +11757,111 @@ function applyI18nLocale(record, i18nFields, locale, fallback, layer = "read") {
11579
11757
  // src/money/normalize.ts
11580
11758
  init_fixed_point();
11581
11759
  init_descriptor();
11760
+
11761
+ // src/money/paths.ts
11762
+ init_errors();
11763
+ var SEGMENT_RE = /^(\*|[^.[\]*]+)(\[\])?$/;
11764
+ var parseCache = /* @__PURE__ */ new Map();
11765
+ function parseMoneyPath(path) {
11766
+ const cached = parseCache.get(path);
11767
+ if (cached) return cached;
11768
+ if (typeof path !== "string" || path.length === 0) {
11769
+ throw new ValidationError("moneyFields: path must be a non-empty string");
11770
+ }
11771
+ const segments = [];
11772
+ for (const part of path.split(".")) {
11773
+ const m = SEGMENT_RE.exec(part);
11774
+ if (!m) {
11775
+ throw new ValidationError(
11776
+ `moneyFields: invalid path "${path}" \u2014 segment "${part}" must be a key, "key[]", "*", or "*[]"`
11777
+ );
11778
+ }
11779
+ const array = m[2] === "[]";
11780
+ segments.push(
11781
+ m[1] === "*" ? { kind: "wildcard", array } : { kind: "key", key: m[1], array }
11782
+ );
11783
+ }
11784
+ parseCache.set(path, segments);
11785
+ return segments;
11786
+ }
11787
+ function isSimpleMoneyPath(path) {
11788
+ return !path.includes(".") && !path.includes("[") && !path.includes("*");
11789
+ }
11790
+ function validateMoneyFieldPaths(moneyFields) {
11791
+ for (const path of Object.keys(moneyFields)) parseMoneyPath(path);
11792
+ }
11793
+ function transformAtMoneyPath(node, path, segments, index, visit, lenient) {
11794
+ if (node === null || node === void 0) return node;
11795
+ const seg = segments[index];
11796
+ const last = index === segments.length - 1;
11797
+ if (seg.kind === "key") {
11798
+ if (typeof node !== "object" || Array.isArray(node)) {
11799
+ if (lenient) return node;
11800
+ throw new ValidationError(
11801
+ `moneyFields: path "${path}" expected an object at segment "${seg.key}", got ${Array.isArray(node) ? "an array" : typeof node}`
11802
+ );
11803
+ }
11804
+ const obj2 = node;
11805
+ if (!(seg.key in obj2) || obj2[seg.key] === null || obj2[seg.key] === void 0) return node;
11806
+ if (seg.array) {
11807
+ const arr = obj2[seg.key];
11808
+ if (!Array.isArray(arr)) {
11809
+ if (lenient) return node;
11810
+ throw new ValidationError(
11811
+ `moneyFields: path "${path}" declares "${seg.key}[]" but the value is not an array`
11812
+ );
11813
+ }
11814
+ const cloned = [...arr];
11815
+ if (last) {
11816
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
11817
+ } else {
11818
+ for (let i = 0; i < cloned.length; i++) {
11819
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
11820
+ }
11821
+ }
11822
+ return { ...obj2, [seg.key]: cloned };
11823
+ }
11824
+ const clone3 = { ...obj2 };
11825
+ if (last) {
11826
+ visit(clone3, seg.key);
11827
+ } else {
11828
+ clone3[seg.key] = transformAtMoneyPath(clone3[seg.key], path, segments, index + 1, visit, lenient);
11829
+ }
11830
+ return clone3;
11831
+ }
11832
+ if (seg.array) {
11833
+ if (!Array.isArray(node)) {
11834
+ if (lenient) return node;
11835
+ throw new ValidationError(`moneyFields: path "${path}" declares "*[]" but the value is not an array`);
11836
+ }
11837
+ const cloned = [...node];
11838
+ if (last) {
11839
+ for (let i = 0; i < cloned.length; i++) visit(cloned, i);
11840
+ } else {
11841
+ for (let i = 0; i < cloned.length; i++) {
11842
+ cloned[i] = transformAtMoneyPath(cloned[i], path, segments, index + 1, visit, lenient);
11843
+ }
11844
+ }
11845
+ return cloned;
11846
+ }
11847
+ if (typeof node !== "object" || Array.isArray(node)) {
11848
+ if (lenient) return node;
11849
+ throw new ValidationError(
11850
+ `moneyFields: path "${path}" applies "*" to a non-object (${Array.isArray(node) ? 'array \u2014 use "*[]"' : typeof node})`
11851
+ );
11852
+ }
11853
+ const obj = node;
11854
+ const clone2 = { ...obj };
11855
+ for (const key of Object.keys(obj)) {
11856
+ const v = clone2[key];
11857
+ if (v === null || v === void 0) continue;
11858
+ if (last) visit(clone2, key);
11859
+ else clone2[key] = transformAtMoneyPath(v, path, segments, index + 1, visit, lenient);
11860
+ }
11861
+ return clone2;
11862
+ }
11863
+
11864
+ // src/money/normalize.ts
11582
11865
  function isMoneyValueObject(v) {
11583
11866
  return typeof v === "object" && v !== null && "currency" in v;
11584
11867
  }
@@ -11590,33 +11873,68 @@ function quantizeAmount(field, input, scale, rounding) {
11590
11873
  }
11591
11874
  return r.value.toString();
11592
11875
  }
11876
+ function canonicalizeStoredMoney(record, moneyFields) {
11877
+ if (record === null || record === void 0) return record;
11878
+ if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
11879
+ return decodeMoneyFields(record, moneyFields, "raw");
11880
+ }
11881
+ function canonicalizeIncomingMoney(record, moneyFields) {
11882
+ if (!moneyFields || Object.keys(moneyFields).length === 0) return record;
11883
+ try {
11884
+ return decodeMoneyFields(
11885
+ quantizeMoneyFields(record, moneyFields),
11886
+ moneyFields,
11887
+ "raw"
11888
+ );
11889
+ } catch {
11890
+ return record;
11891
+ }
11892
+ }
11893
+ function quantizeValue(field, raw, desc) {
11894
+ if (desc.mode === "fixed") {
11895
+ const currency2 = desc.fixedCurrency;
11896
+ return quantizeAmount(field, raw, desc.scaleFor(currency2), desc.rounding);
11897
+ }
11898
+ let amount;
11899
+ let currency;
11900
+ if (isMoneyValueObject(raw)) {
11901
+ currency = String(raw.currency);
11902
+ amount = raw.amount;
11903
+ } else {
11904
+ const sole = desc.soleCurrency();
11905
+ if (sole === void 0) {
11906
+ throw new TypeError(
11907
+ `money: field "${field}" is multi-currency \u2014 write { amount, currency }, not a bare amount`
11908
+ );
11909
+ }
11910
+ currency = sole;
11911
+ amount = raw;
11912
+ }
11913
+ const scale = desc.scaleFor(currency);
11914
+ return { amount: quantizeAmount(field, amount, scale, desc.rounding), currency };
11915
+ }
11593
11916
  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);
11917
+ let out = { ...record };
11918
+ for (const [path, desc] of Object.entries(moneyFields)) {
11919
+ if (isSimpleMoneyPath(path)) {
11920
+ const raw = out[path];
11921
+ if (raw === null || raw === void 0) continue;
11922
+ out[path] = quantizeValue(path, raw, desc);
11601
11923
  continue;
11602
11924
  }
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 };
11925
+ out = transformAtMoneyPath(
11926
+ out,
11927
+ path,
11928
+ parseMoneyPath(path),
11929
+ 0,
11930
+ (container, key) => {
11931
+ const raw = container[key];
11932
+ if (raw === null || raw === void 0) return;
11933
+ container[key] = quantizeValue(path, raw, desc);
11934
+ },
11935
+ /* lenient */
11936
+ false
11937
+ );
11620
11938
  }
11621
11939
  return out;
11622
11940
  }
@@ -11629,33 +11947,70 @@ function formatCurrency(decimal, currency, scale, locale) {
11629
11947
  });
11630
11948
  return fmt.format(decimal);
11631
11949
  }
11950
+ function decodeValue(stored, desc) {
11951
+ let currency;
11952
+ let scaledIntString;
11953
+ if (desc.mode === "fixed") {
11954
+ if (typeof stored !== "string" && typeof stored !== "number") return null;
11955
+ currency = desc.fixedCurrency;
11956
+ scaledIntString = String(stored);
11957
+ } else {
11958
+ if (!isMoneyValueObject(stored)) return null;
11959
+ const amount = stored.amount;
11960
+ if (typeof stored.currency !== "string" || typeof amount !== "string" && typeof amount !== "number") return null;
11961
+ currency = stored.currency;
11962
+ scaledIntString = String(amount);
11963
+ }
11964
+ const scale = desc.scaleFor(currency);
11965
+ let decimal;
11966
+ try {
11967
+ decimal = formatScaledInt(BigInt(scaledIntString), scale);
11968
+ } catch {
11969
+ return null;
11970
+ }
11971
+ return {
11972
+ decoded: desc.mode === "fixed" ? decimal : { amount: decimal, currency },
11973
+ decimal,
11974
+ currency,
11975
+ scale
11976
+ };
11977
+ }
11632
11978
  function decodeMoneyFields(record, moneyFields, locale) {
11633
- const out = { ...record };
11979
+ let out = { ...record };
11634
11980
  const format = locale !== "raw";
11635
11981
  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);
11982
+ for (const [path, desc] of Object.entries(moneyFields)) {
11983
+ if (isSimpleMoneyPath(path)) {
11984
+ const stored = out[path];
11985
+ if (stored === null || stored === void 0) continue;
11986
+ const r = decodeValue(stored, desc);
11987
+ if (r === null) continue;
11988
+ out[path] = r.decoded;
11989
+ if (format) {
11990
+ out[`${path}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
11991
+ out[`${path}Number`] = Number(r.decimal);
11992
+ }
11993
+ continue;
11658
11994
  }
11995
+ out = transformAtMoneyPath(
11996
+ out,
11997
+ path,
11998
+ parseMoneyPath(path),
11999
+ 0,
12000
+ (container, key) => {
12001
+ const stored = container[key];
12002
+ if (stored === null || stored === void 0) return;
12003
+ const r = decodeValue(stored, desc);
12004
+ if (r === null) return;
12005
+ container[key] = r.decoded;
12006
+ if (format && typeof key === "string" && !Array.isArray(container)) {
12007
+ container[`${key}Formatted`] = formatCurrency(r.decimal, r.currency, r.scale, fmtLocale);
12008
+ container[`${key}Number`] = Number(r.decimal);
12009
+ }
12010
+ },
12011
+ /* lenient */
12012
+ true
12013
+ );
11659
12014
  }
11660
12015
  return out;
11661
12016
  }
@@ -12008,6 +12363,7 @@ var NO_AGGREGATE = {
12008
12363
 
12009
12364
  // src/query/builder.ts
12010
12365
  init_money_reducer();
12366
+ init_where();
12011
12367
  var EMPTY_PLAN = {
12012
12368
  clauses: [],
12013
12369
  orderBy: [],
@@ -12099,9 +12455,18 @@ var Query = class _Query {
12099
12455
  this.predicates
12100
12456
  );
12101
12457
  }
12102
- /** Add a field comparison. Multiple where() calls are AND-combined. */
12458
+ /**
12459
+ * Add a field comparison. Multiple where() calls are AND-combined.
12460
+ *
12461
+ * A declared money field compares in MAJOR units (#336): the operand
12462
+ * (`10000`, `'10000.00'`, or `{ amount, currency }` in multi mode) is
12463
+ * quantized into stored scaled-int space at build time and evaluated
12464
+ * BigInt-exact per record. A malformed operand or a string operator
12465
+ * (`contains`/`startsWith`) throws here, at the call site.
12466
+ */
12103
12467
  where(field, op, value) {
12104
- const clause = { type: "field", field, op, value };
12468
+ const desc = this.source.moneyFields?.[field];
12469
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
12105
12470
  return new _Query(
12106
12471
  this.source,
12107
12472
  { ...this.plan, clauses: [...this.plan.clauses, clause] },
@@ -12427,7 +12792,7 @@ var Query = class _Query {
12427
12792
  }
12428
12793
  const { candidates, remainingClauses } = candidateRecords(this.source, this.plan.clauses);
12429
12794
  if (remainingClauses.length === 0) return candidates.length;
12430
- return filterRecords(candidates, remainingClauses).length;
12795
+ return filterRecords(candidates, remainingClauses, fnViewDecoder(this.source)).length;
12431
12796
  }
12432
12797
  /**
12433
12798
  * Reduce the matching records through a named set of reducers.
@@ -12484,7 +12849,7 @@ var Query = class _Query {
12484
12849
  return executeClausePipeline(source, clauses, joinCtx);
12485
12850
  }
12486
12851
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
12487
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
12852
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
12488
12853
  };
12489
12854
  const upstreams = [];
12490
12855
  if (source.subscribe) {
@@ -12507,7 +12872,7 @@ var Query = class _Query {
12507
12872
  return executeClausePipeline(source, clauses, joinCtx);
12508
12873
  }
12509
12874
  const { candidates, remainingClauses } = candidateRecords(source, clauses);
12510
- return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses);
12875
+ return remainingClauses.length === 0 ? candidates : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
12511
12876
  };
12512
12877
  const upstreams = [];
12513
12878
  if (source.subscribe) {
@@ -12659,7 +13024,7 @@ function executePlanWithSource(source, plan, joinContext) {
12659
13024
  result = executeClausePipeline(source, plan.clauses, joinContext);
12660
13025
  } else {
12661
13026
  const { candidates, remainingClauses } = candidateRecords(source, plan.clauses);
12662
- result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses);
13027
+ result = remainingClauses.length === 0 ? [...candidates] : filterRecords(candidates, remainingClauses, fnViewDecoder(source));
12663
13028
  }
12664
13029
  if (plan.orderBy.length > 0) {
12665
13030
  result = sortRecords(result, plan.orderBy);
@@ -12682,6 +13047,7 @@ function candidateRecords(source, clauses) {
12682
13047
  const clause = clauses[i];
12683
13048
  if (clause.type !== "field") continue;
12684
13049
  if (!indexes.has(clause.field)) continue;
13050
+ if (clause.money?.mode === "multi") continue;
12685
13051
  let ids = null;
12686
13052
  if (clause.op === "==") {
12687
13053
  ids = indexes.lookupEqual(clause.field, clause.value);
@@ -12727,13 +13093,20 @@ function executePlan(records, plan) {
12727
13093
  }
12728
13094
  return result;
12729
13095
  }
12730
- function filterRecords(records, clauses) {
13096
+ function fnViewDecoder(source) {
13097
+ const mf = source.moneyFields;
13098
+ if (!mf || Object.keys(mf).length === 0) return void 0;
13099
+ return (r) => decodeMoneyFields(r, mf, "raw");
13100
+ }
13101
+ function filterRecords(records, clauses, decodeForFns) {
12731
13102
  if (clauses.length === 0) return [...records];
13103
+ const needsFnView = decodeForFns !== void 0 && hasFnClause(clauses);
12732
13104
  const out = [];
12733
13105
  for (const r of records) {
13106
+ const fnView = needsFnView ? decodeForFns(r) : void 0;
12734
13107
  let matches = true;
12735
13108
  for (const clause of clauses) {
12736
- if (!evaluateClause(r, clause)) {
13109
+ if (!evaluateClause(r, clause, fnView)) {
12737
13110
  matches = false;
12738
13111
  break;
12739
13112
  }
@@ -12745,10 +13118,11 @@ function filterRecords(records, clauses) {
12745
13118
  function executeClausePipeline(source, clauses, joinContext) {
12746
13119
  let rel = [...source.snapshot()];
12747
13120
  let filterBatch = [];
13121
+ const decodeForFns = fnViewDecoder(source);
12748
13122
  for (const clause of clauses) {
12749
13123
  if (clause.type === "crossJoin") {
12750
13124
  if (filterBatch.length > 0) {
12751
- rel = filterRecords(rel, filterBatch);
13125
+ rel = filterRecords(rel, filterBatch, decodeForFns);
12752
13126
  filterBatch = [];
12753
13127
  }
12754
13128
  const rightSource = joinContext.resolveSource(clause.target);
@@ -12761,7 +13135,7 @@ function executeClausePipeline(source, clauses, joinContext) {
12761
13135
  }
12762
13136
  }
12763
13137
  if (filterBatch.length > 0) {
12764
- rel = filterRecords(rel, filterBatch);
13138
+ rel = filterRecords(rel, filterBatch, decodeForFns);
12765
13139
  }
12766
13140
  return rel;
12767
13141
  }
@@ -13179,6 +13553,7 @@ init_groupby();
13179
13553
  // src/query/scan-builder.ts
13180
13554
  init_predicate();
13181
13555
  init_errors();
13556
+ init_where();
13182
13557
  var DEFAULT_SCAN_PAGE_SIZE = 100;
13183
13558
  var ScanBuilder = class _ScanBuilder {
13184
13559
  pageProvider;
@@ -13242,7 +13617,8 @@ var ScanBuilder = class _ScanBuilder {
13242
13617
  * evaluates clauses per record in O(1) per clause.
13243
13618
  */
13244
13619
  where(field, op, value) {
13245
- const clause = { type: "field", field, op, value };
13620
+ const desc = this.moneyFields?.[field];
13621
+ const clause = desc ? moneyFieldClause(field, op, value, desc) : { type: "field", field, op, value };
13246
13622
  return new _ScanBuilder(
13247
13623
  this.pageProvider,
13248
13624
  this.pageSize,
@@ -13588,8 +13964,9 @@ var ScanBuilder = class _ScanBuilder {
13588
13964
  */
13589
13965
  recordMatches(record) {
13590
13966
  if (this.clauses.length === 0) return true;
13967
+ const fnView = this.moneyFields && Object.keys(this.moneyFields).length > 0 && hasFnClause(this.clauses) ? this.decodeMoney(record) : void 0;
13591
13968
  for (const clause of this.clauses) {
13592
- if (!evaluateClause(record, clause)) return false;
13969
+ if (!evaluateClause(record, clause, fnView)) return false;
13593
13970
  }
13594
13971
  return true;
13595
13972
  }
@@ -14753,6 +15130,7 @@ var Collection = class {
14753
15130
  this.joinResolver = opts.joinResolver;
14754
15131
  this.i18nFields = opts.i18nFields;
14755
15132
  this.dictKeyFields = opts.dictKeyFields;
15133
+ if (opts.moneyFields) validateMoneyFieldPaths(opts.moneyFields);
14756
15134
  this.moneyFields = opts.moneyFields;
14757
15135
  this.computed = opts.computed;
14758
15136
  this.dictLabelResolver = opts.dictLabelResolver;
@@ -14886,7 +15264,9 @@ var Collection = class {
14886
15264
  * declaration; this reconciles that ordering. First-wins. Not public.
14887
15265
  */
14888
15266
  _applyMoneyFields(moneyFields) {
14889
- if (this.moneyFields === void 0) this.moneyFields = moneyFields;
15267
+ if (this.moneyFields !== void 0) return;
15268
+ validateMoneyFieldPaths(moneyFields);
15269
+ this.moneyFields = moneyFields;
14890
15270
  }
14891
15271
  /** @internal — attach computed fields post-construction. See {@link _applyMoneyFields}. */
14892
15272
  _applyComputed(computed) {
@@ -15053,6 +15433,7 @@ var Collection = class {
15053
15433
  if (!hasWritePermission(this.keyring, this.name)) {
15054
15434
  throw new ReadOnlyError();
15055
15435
  }
15436
+ record = canonicalizeIncomingMoney(record, this.moneyFields);
15056
15437
  if (this.subsystemBus?.hasGateHandlers("beforePut")) {
15057
15438
  const existingEnv = await this.adapter.get(this.vault, this.name, id);
15058
15439
  let existingRecord = null;
@@ -15069,7 +15450,7 @@ var Collection = class {
15069
15450
  collection: this.name,
15070
15451
  docId: id,
15071
15452
  incoming: record,
15072
- existing: existingRecord,
15453
+ existing: canonicalizeStoredMoney(existingRecord, this.moneyFields),
15073
15454
  existingVersion: existingEnv?._v ?? 0,
15074
15455
  existingTs: existingEnv?._ts,
15075
15456
  userId: this.keyring.userId,
@@ -15338,7 +15719,7 @@ var Collection = class {
15338
15719
  */
15339
15720
  async dispatchDerivations(id, record, version) {
15340
15721
  if (this.derivationSource === void 0) return;
15341
- const incoming = record;
15722
+ const incoming = canonicalizeStoredMoney(record, this.moneyFields);
15342
15723
  if (incoming && typeof incoming === "object" && "_derivedFrom" in incoming) return;
15343
15724
  const registry = this.derivationSource.registry();
15344
15725
  const strategies = registry.strategiesForSource(this.name);
@@ -15571,7 +15952,7 @@ var Collection = class {
15571
15952
  vault: this.vault,
15572
15953
  collection: this.name,
15573
15954
  docId: id,
15574
- existing: existingRecord,
15955
+ existing: canonicalizeStoredMoney(existingRecord, this.moneyFields),
15575
15956
  existingVersion: existingEnv._v,
15576
15957
  existingTs: existingEnv._ts,
15577
15958
  internal,
@@ -25768,9 +26149,9 @@ function withMaterializedView(spec) {
25768
26149
  throw new ValidationError("withMaterializedView: query must be a function returning a Query<T>");
25769
26150
  }
25770
26151
  if (spec.unionSources) {
25771
- if (spec.unionSources.length < 2) {
26152
+ if (spec.unionSources.length < 1) {
25772
26153
  throw new MaterializedViewConfigError(
25773
- "unionSources requires at least 2 source collections"
26154
+ "unionSources requires at least 1 source collection"
25774
26155
  );
25775
26156
  }
25776
26157
  const seen = /* @__PURE__ */ new Set();
@@ -25862,6 +26243,117 @@ init_errors();
25862
26243
  init_descriptor();
25863
26244
  init_iso4217();
25864
26245
 
26246
+ // src/money/arith.ts
26247
+ init_fixed_point();
26248
+ init_descriptor();
26249
+ function parseAmount(label, amount, scale, rounding) {
26250
+ const r = parseToScaledInt(amount, scale, rounding);
26251
+ if (!r.ok) {
26252
+ throw new MoneyUnsupportedError(
26253
+ 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`
26254
+ );
26255
+ }
26256
+ return r.value;
26257
+ }
26258
+ function resolveScale(label, amount, explicit) {
26259
+ if (explicit !== void 0) {
26260
+ if (!Number.isInteger(explicit) || explicit < 0) {
26261
+ throw new MoneyUnsupportedError(`${label}: scale must be a non-negative integer`);
26262
+ }
26263
+ return explicit;
26264
+ }
26265
+ const inferred = decimalScaleOf(amount);
26266
+ if (inferred === null) {
26267
+ throw new MoneyUnsupportedError(`${label}: amount ${JSON.stringify(amount)} is not a finite decimal`);
26268
+ }
26269
+ return inferred;
26270
+ }
26271
+ function mulRate(amount, rate, opts = {}) {
26272
+ const scale = resolveScale("mulRate", amount, opts.scale);
26273
+ const rounding = opts.rounding ?? "half-up";
26274
+ const a = parseAmount("mulRate", amount, scale, rounding);
26275
+ const rateScale = decimalScaleOf(rate);
26276
+ if (rateScale === null) {
26277
+ throw new MoneyUnsupportedError(`mulRate: rate ${JSON.stringify(rate)} is not a finite decimal`);
26278
+ }
26279
+ const r = parseToScaledInt(rate, rateScale);
26280
+ if (!r.ok) {
26281
+ throw new MoneyUnsupportedError(`mulRate: rate ${JSON.stringify(rate)} is not a finite decimal`);
26282
+ }
26283
+ const product = a * r.value;
26284
+ return formatScaledInt(rescaleScaledInt(product, scale + rateScale, scale, rounding), scale);
26285
+ }
26286
+ function allocate(amount, weights, opts = {}) {
26287
+ if (weights.length === 0) {
26288
+ throw new MoneyUnsupportedError("allocate: weights must not be empty");
26289
+ }
26290
+ const scale = resolveScale("allocate", amount, opts.scale);
26291
+ const a = parseAmount("allocate", amount, scale);
26292
+ let weightScale = 0;
26293
+ for (const w of weights) {
26294
+ const s = decimalScaleOf(w);
26295
+ if (s === null) {
26296
+ throw new MoneyUnsupportedError(`allocate: weight ${JSON.stringify(w)} is not a finite decimal`);
26297
+ }
26298
+ if (s > weightScale) weightScale = s;
26299
+ }
26300
+ const scaledWeights = weights.map((w) => {
26301
+ const r = parseToScaledInt(w, weightScale);
26302
+ if (!r.ok || r.value < 0n) {
26303
+ throw new MoneyUnsupportedError(`allocate: weight ${JSON.stringify(w)} must be a non-negative decimal`);
26304
+ }
26305
+ return r.value;
26306
+ });
26307
+ const sumW = scaledWeights.reduce((acc, w) => acc + w, 0n);
26308
+ if (sumW === 0n) {
26309
+ throw new MoneyUnsupportedError("allocate: weights must not all be zero");
26310
+ }
26311
+ const negative = a < 0n;
26312
+ const mag = negative ? -a : a;
26313
+ const base = [];
26314
+ const remainders = [];
26315
+ let distributed = 0n;
26316
+ for (let i = 0; i < scaledWeights.length; i++) {
26317
+ const product = mag * scaledWeights[i];
26318
+ const share = product / sumW;
26319
+ base.push(share);
26320
+ distributed += share;
26321
+ remainders.push({ index: i, rem: product % sumW });
26322
+ }
26323
+ let leftover = mag - distributed;
26324
+ remainders.sort((x, y) => y.rem > x.rem ? 1 : y.rem < x.rem ? -1 : x.index - y.index);
26325
+ for (const { index } of remainders) {
26326
+ if (leftover === 0n) break;
26327
+ base[index] = base[index] + 1n;
26328
+ leftover -= 1n;
26329
+ }
26330
+ return base.map((p) => formatScaledInt(negative && p !== 0n ? -p : p, scale));
26331
+ }
26332
+
26333
+ // src/money/branded.ts
26334
+ init_fixed_point();
26335
+ init_descriptor();
26336
+ function asMoney(value) {
26337
+ if (!isMoneyLike(value)) {
26338
+ throw new MoneyUnsupportedError(`asMoney: ${JSON.stringify(value)} is not a finite decimal`);
26339
+ }
26340
+ return String(value).trim();
26341
+ }
26342
+ function isMoneyString(value) {
26343
+ return typeof value === "string" && isMoneyLike(value);
26344
+ }
26345
+ function moneyNumber(value) {
26346
+ if (!isMoneyLike(value)) {
26347
+ throw new MoneyUnsupportedError(`moneyNumber: ${JSON.stringify(value)} is not a finite decimal`);
26348
+ }
26349
+ return Number(value);
26350
+ }
26351
+ function isMoneyLike(value) {
26352
+ if (typeof value === "number") return Number.isFinite(value);
26353
+ if (typeof value !== "string") return false;
26354
+ return decimalScaleOf(value) !== null;
26355
+ }
26356
+
25865
26357
  // src/i18n/script.ts
25866
26358
  init_errors();
25867
26359
  var LATIN_BASE = /* @__PURE__ */ new Set([
@@ -26804,9 +27296,11 @@ function shortJSON(value) {
26804
27296
  WeakPassphraseError,
26805
27297
  activeSessionCount,
26806
27298
  additiveOnly,
27299
+ allocate,
26807
27300
  applyI18nLocale,
26808
27301
  applyJoins,
26809
27302
  applyPatch,
27303
+ asMoney,
26810
27304
  assertStrongPassphrase,
26811
27305
  assertTierAccess,
26812
27306
  avg,
@@ -26883,6 +27377,7 @@ function shortJSON(value) {
26883
27377
  isI18nTextDescriptor,
26884
27378
  isMagicLinkGrantExpired,
26885
27379
  isMoneyDescriptor,
27380
+ isMoneyString,
26886
27381
  isPreCompressed,
26887
27382
  isPublicEnvelope,
26888
27383
  isSessionAlive,
@@ -26915,6 +27410,8 @@ function shortJSON(value) {
26915
27410
  mintShamirRecoveryEntry,
26916
27411
  mintWrappedDeksBlob,
26917
27412
  money,
27413
+ moneyNumber,
27414
+ mulRate,
26918
27415
  paddedIndex,
26919
27416
  parseBytes,
26920
27417
  parseIndex,