@amboras-dev/money 0.0.0-stage → 0.2.0

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 (78) hide show
  1. package/README.md +401 -2
  2. package/dist/ai.cjs +185 -0
  3. package/dist/ai.d.cts +97 -0
  4. package/dist/ai.d.ts +97 -0
  5. package/dist/ai.js +175 -0
  6. package/dist/allocate-CZRg4TOP.cjs +222 -0
  7. package/dist/allocate-X7_jAtOj.js +199 -0
  8. package/dist/amount-BV0O-aN8.cjs +241 -0
  9. package/dist/amount-BoYBMgK9.js +200 -0
  10. package/dist/arithmetic-BhNwisjb.js +132 -0
  11. package/dist/arithmetic-DjT4Kzgt.cjs +233 -0
  12. package/dist/billing.cjs +184 -0
  13. package/dist/billing.d.cts +81 -0
  14. package/dist/billing.d.ts +81 -0
  15. package/dist/billing.js +175 -0
  16. package/dist/commerce.cjs +536 -0
  17. package/dist/commerce.d.cts +293 -0
  18. package/dist/commerce.d.ts +293 -0
  19. package/dist/commerce.js +516 -0
  20. package/dist/format-BdPnijvn.js +99 -0
  21. package/dist/format-DLhno7eX.cjs +128 -0
  22. package/dist/fx.cjs +407 -0
  23. package/dist/fx.d.cts +157 -0
  24. package/dist/fx.d.ts +157 -0
  25. package/dist/fx.js +374 -0
  26. package/dist/index.cjs +352 -0
  27. package/dist/index.d.cts +300 -0
  28. package/dist/index.d.ts +300 -0
  29. package/dist/index.js +276 -0
  30. package/dist/input.cjs +163 -0
  31. package/dist/input.d.cts +203 -0
  32. package/dist/input.d.ts +203 -0
  33. package/dist/input.js +153 -0
  34. package/dist/iso-minor-CqkWDxHS.js +48 -0
  35. package/dist/iso-minor-wSwJN57T.cjs +65 -0
  36. package/dist/medusa-4K8N1pUc.d.cts +87 -0
  37. package/dist/medusa-DBXJXXvF.d.ts +87 -0
  38. package/dist/medusa-DqJY1qpj.js +116 -0
  39. package/dist/medusa-lRY9xlsf.cjs +175 -0
  40. package/dist/money-B-3aFjNJ.d.cts +248 -0
  41. package/dist/money-B-3aFjNJ.d.ts +248 -0
  42. package/dist/money-B3wH_Z4S.cjs +3990 -0
  43. package/dist/money-WhnGiI00.js +3583 -0
  44. package/dist/number-text-BWu67jUq.cjs +145 -0
  45. package/dist/number-text-DE-q4jO5.js +134 -0
  46. package/dist/own-D7544D3k.js +4 -0
  47. package/dist/own-oP9DXFWv.cjs +9 -0
  48. package/dist/percent-C0InC2t2.cjs +43 -0
  49. package/dist/percent-D1VB9xXG.js +20 -0
  50. package/dist/rate-CVeMii4a.cjs +217 -0
  51. package/dist/rate-DBPiYgT1.d.ts +63 -0
  52. package/dist/rate-DicStBue.js +152 -0
  53. package/dist/rate-tqK8ijG0.d.cts +63 -0
  54. package/dist/reporting-CG-ai2sM.d.cts +31 -0
  55. package/dist/reporting-tpGJbqYp.d.ts +31 -0
  56. package/dist/retail-1mu888J1.js +69 -0
  57. package/dist/retail-C_BPLvMj.d.cts +31 -0
  58. package/dist/retail-Cml8YSKg.cjs +86 -0
  59. package/dist/retail-DRUTTQoJ.d.ts +31 -0
  60. package/dist/rounding-DHBtNCHG.cjs +79 -0
  61. package/dist/rounding-g9rvye0k.js +50 -0
  62. package/dist/sql.cjs +290 -0
  63. package/dist/sql.d.cts +147 -0
  64. package/dist/sql.d.ts +147 -0
  65. package/dist/sql.js +271 -0
  66. package/dist/testing.cjs +61995 -0
  67. package/dist/testing.d.cts +125 -0
  68. package/dist/testing.d.ts +125 -0
  69. package/dist/testing.js +61986 -0
  70. package/dist/wire-Bhuut8cW.cjs +2097 -0
  71. package/dist/wire-CaUN9Vf_.d.ts +309 -0
  72. package/dist/wire-sW0yzqth.js +1888 -0
  73. package/dist/wire-xjYXv4go.d.cts +309 -0
  74. package/dist/wire.cjs +37 -0
  75. package/dist/wire.d.cts +2 -0
  76. package/dist/wire.d.ts +2 -0
  77. package/dist/wire.js +2 -0
  78. package/package.json +158 -3
@@ -0,0 +1,81 @@
1
+ import { R as DecimalText, i as Money } from "./money-B-3aFjNJ.js";
2
+ //#region src/billing/usage.d.ts
3
+ /** USD with sub-cent precision, as canonical decimal text (`'0.001234'`). Never merchant money. */
4
+ type UsdAmount = DecimalText;
5
+ interface UsdCostLine {
6
+ /** Tokens, seconds, images...; negative for an adjustment. */
7
+ quantity: number;
8
+ /** USD per million units (token prices). Give this or `usdPerUnit`. */
9
+ usdPerMillion?: number | DecimalText;
10
+ /** USD per unit. */
11
+ usdPerUnit?: number | DecimalText;
12
+ /** A surcharge multiplier (long-context 2x, 1.5x). Default 1. */
13
+ multiplier?: number | DecimalText;
14
+ }
15
+ /**
16
+ * The exact sum of `quantity x rate x multiplier` over `lines`, rounded half-up once to 6
17
+ * decimals. Replaces the token-cost formulas (`computeCost`, the unpriced-model fallback,
18
+ * the cloner's `estimateCost`). Each line needs exactly one of `usdPerMillion` / `usdPerUnit`.
19
+ */
20
+ export declare function usdCost(lines: readonly UsdCostLine[]): UsdAmount;
21
+ //#endregion
22
+ //#region src/billing/credits.d.ts
23
+ /**
24
+ * Credits for a USD cost: `ceil(usd x 100)` (0.07 gives 7, 1.1 gives 110, 0.0049 and 1e-7
25
+ * give 1). Text and Money are exact. A number is first rounded half-even to 8 decimals, so
26
+ * float noise never adds a credit (`0.07 * 3` gives 21, `1.1 * 1.1` gives 121). USD Money is
27
+ * cents already. A negative cost throws `amount_invalid`.
28
+ */
29
+ export declare function usdToCredits(usd: UsdAmount | number | Money): number;
30
+ /** The USD a number of credits is worth: `credits / 100`, exact. */
31
+ export declare function creditsToUsd(credits: number): UsdAmount;
32
+ /**
33
+ * The credits charged for a share (0..1) of a quoted run: `min(quoted, ceil(quoted x share))`,
34
+ * with the product held to 6 decimals (half-up) before the ceil, as the quote does, so a float
35
+ * share such as 0.28000000000000003 never adds a credit.
36
+ */
37
+ export declare function prorateCredits(quoted: number, share: number | DecimalText): number;
38
+ /** Credits rounded up to a whole `step` (default 50) for a quote: `ceil(credits / step) x step`. */
39
+ export declare function bucketCredits(credits: number, step?: number): number;
40
+ //#endregion
41
+ //#region src/billing/plans.d.ts
42
+ /** The yearly price: `yearly` when set, else `monthly x 12`. */
43
+ export declare function annualPrice(input: {
44
+ monthly: Money;
45
+ yearly?: Money | null;
46
+ }): Money;
47
+ /** `yearly / 12` half-up to a storage unit (a cent); whole-dollar headlines use `roundToUnit(..., { unit: 'major' })`. */
48
+ export declare function monthlyEquivalent(yearly: Money): Money;
49
+ type Coupon = {
50
+ readonly percentOff: number | DecimalText;
51
+ } | {
52
+ readonly amountOff: Money;
53
+ };
54
+ /**
55
+ * `m` after `coupons`. `'sequential'` (default, Stripe): in order, each on what the previous
56
+ * left, a percent taking `round(amount x pct / 100)` half-up, clamped at 0 after each.
57
+ * `'additive'`: the percents summed and taken in one rounding, plus the amounts, clamped at 0.
58
+ * An amount-off coupon in another currency throws `currency_mismatch`.
59
+ */
60
+ export declare function applyCoupons(m: Money, coupons: readonly Coupon[], options?: {
61
+ mode?: 'sequential' | 'additive';
62
+ }): Money;
63
+ /**
64
+ * The line amount that is still `m` after the subscription's percent coupons take their cut
65
+ * (Stripe discounts a negative invoice line too). A verbatim port of orch `grossUpCredit`,
66
+ * float arithmetic included, because a credit already granted must not change by a cent:
67
+ * `Math.round(m / prod(1 - p / 100))` with the product taken in float64, so a tie is resolved
68
+ * by the float product (9 cents at [10, 20] gives 12, since 0.9 x 0.8 is 0.7200000000000001);
69
+ * when the kept share is not positive (100% off), `m` unchanged.
70
+ * It inverts `applyCoupons` exactly for one percent coupon. For stacked percents it can miss
71
+ * by a cent (about 4% of amounts at [10, 20] or [30, 15]): Stripe rounds each coupon's cut,
72
+ * while this divides by the unrounded product (11 cents at [10, 20] grosses up to 15, which
73
+ * the coupons take to 10).
74
+ */
75
+ export declare function grossUpForPercentOffs(m: Money, percents: readonly (number | DecimalText)[]): Money;
76
+ //#endregion
77
+ //#region src/billing.d.ts
78
+ /** USD cents: Money in USD (USD stored amounts are cents). */
79
+ export type UsdCents = Money;
80
+ //#endregion
81
+ export type { Coupon, UsdAmount, UsdCostLine };
@@ -0,0 +1,175 @@
1
+ import { $ as describeValue, B as decimalOf, Q as MoneyError, dt as roundToScale, et as addDecimal, it as formatDecimal, n as fromMajor, pt as toDecimalText, rt as divRound, s as storedDecimal, st as multiplyDecimal, t as boundedMoney } from "./money-WhnGiI00.js";
2
+ import { n as hundredPercent, r as percentOfUnits } from "./percent-D1VB9xXG.js";
3
+ //#region src/billing/credits.ts
4
+ function wholeNumber(value, name, min) {
5
+ if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min) throw new MoneyError("amount_invalid", `${name} must be an integer >= ${min}, got ${describeValue(value)}`);
6
+ return BigInt(value);
7
+ }
8
+ /**
9
+ * Fraction digits a float USD cost is held to before the ceil: 1e-8 USD, the granularity of
10
+ * orch `costUsdToCredits`' 1e-6-credit drift epsilon. Coarse enough to absorb float noise
11
+ * (`0.07 * 3` is 0.21000000000000002), fine enough to keep sub-micro costs (1e-7 gives 1).
12
+ */
13
+ const FLOAT_USD_DIGITS = 8;
14
+ /**
15
+ * Credits for a USD cost: `ceil(usd x 100)` (0.07 gives 7, 1.1 gives 110, 0.0049 and 1e-7
16
+ * give 1). Text and Money are exact. A number is first rounded half-even to 8 decimals, so
17
+ * float noise never adds a credit (`0.07 * 3` gives 21, `1.1 * 1.1` gives 121). USD Money is
18
+ * cents already. A negative cost throws `amount_invalid`.
19
+ */
20
+ function usdToCredits(usd) {
21
+ let cents;
22
+ if (typeof usd === "object" && usd !== null) {
23
+ const { info } = storedDecimal(usd);
24
+ if (info.code !== "USD") throw new MoneyError("currency_mismatch", `Credits are USD cents, got ${info.code}`);
25
+ cents = usd.amount;
26
+ } else {
27
+ const parsed = decimalOf(usd, true, "usd");
28
+ const exact = formatDecimal(typeof usd === "number" ? roundToScale(parsed, FLOAT_USD_DIGITS, "half-even") : parsed);
29
+ cents = fromMajor(exact, "USD", {
30
+ precision: "round",
31
+ rounding: "ceil",
32
+ field: "usd"
33
+ }).amount;
34
+ }
35
+ if (cents < 0) throw new MoneyError("amount_invalid", `A USD cost must not be negative, got ${describeValue(usd)}`);
36
+ return cents;
37
+ }
38
+ /** The USD a number of credits is worth: `credits / 100`, exact. */
39
+ function creditsToUsd(credits) {
40
+ return toDecimalText({
41
+ units: wholeNumber(credits, "credits", Number.MIN_SAFE_INTEGER),
42
+ scale: 2
43
+ });
44
+ }
45
+ /**
46
+ * The credits charged for a share (0..1) of a quoted run: `min(quoted, ceil(quoted x share))`,
47
+ * with the product held to 6 decimals (half-up) before the ceil, as the quote does, so a float
48
+ * share such as 0.28000000000000003 never adds a credit.
49
+ */
50
+ function prorateCredits(quoted, share) {
51
+ const q = wholeNumber(quoted, "quoted", 0);
52
+ const s = decimalOf(share, false, "share");
53
+ const product = roundToScale({
54
+ units: q * s.units,
55
+ scale: s.scale
56
+ }, 6, "half-up");
57
+ const charged = roundToScale(product, 0, "ceil").units;
58
+ return Number(charged < q ? charged : q);
59
+ }
60
+ /** Credits rounded up to a whole `step` (default 50) for a quote: `ceil(credits / step) x step`. */
61
+ function bucketCredits(credits, step = 50) {
62
+ const c = wholeNumber(credits, "credits", Number.MIN_SAFE_INTEGER);
63
+ const s = wholeNumber(step, "step", 1);
64
+ return Number(divRound(c, s, "ceil") * s);
65
+ }
66
+ //#endregion
67
+ //#region src/billing/plans.ts
68
+ function checkSame(m, other, verb) {
69
+ const info = storedDecimal(m).info;
70
+ const own = storedDecimal(other).info;
71
+ if (own.code !== info.code) throw new MoneyError("currency_mismatch", `Cannot ${verb} ${own.code} with ${info.code}`);
72
+ return info;
73
+ }
74
+ /** The yearly price: `yearly` when set, else `monthly x 12`. */
75
+ function annualPrice(input) {
76
+ const info = storedDecimal(input.monthly).info;
77
+ if (input.yearly !== null && input.yearly !== void 0) {
78
+ checkSame(input.monthly, input.yearly, "price");
79
+ return input.yearly;
80
+ }
81
+ return boundedMoney(BigInt(input.monthly.amount) * 12n, info);
82
+ }
83
+ /** `yearly / 12` half-up to a storage unit (a cent); whole-dollar headlines use `roundToUnit(..., { unit: 'major' })`. */
84
+ function monthlyEquivalent(yearly) {
85
+ const { info } = storedDecimal(yearly);
86
+ return boundedMoney(divRound(BigInt(yearly.amount), 12n, "half-up"), info);
87
+ }
88
+ /** A percent in 0..100, else `amount_invalid`. */
89
+ function couponPercent(value, name) {
90
+ const pct = decimalOf(value, false, name);
91
+ if (pct.units > hundredPercent(pct.scale)) throw new MoneyError("amount_invalid", `${name} must be at most 100, got ${describeValue(value)}`);
92
+ return pct;
93
+ }
94
+ /**
95
+ * `m` after `coupons`. `'sequential'` (default, Stripe): in order, each on what the previous
96
+ * left, a percent taking `round(amount x pct / 100)` half-up, clamped at 0 after each.
97
+ * `'additive'`: the percents summed and taken in one rounding, plus the amounts, clamped at 0.
98
+ * An amount-off coupon in another currency throws `currency_mismatch`.
99
+ */
100
+ function applyCoupons(m, coupons, options = {}) {
101
+ const { info } = storedDecimal(m);
102
+ let amount = BigInt(m.amount);
103
+ const percentCut = (base, value) => percentOfUnits(base, couponPercent(value, "percentOff"), "half-up");
104
+ if (options.mode === "additive") {
105
+ let percents = {
106
+ units: 0n,
107
+ scale: 0
108
+ };
109
+ let off = 0n;
110
+ for (const c of coupons) if ("amountOff" in c) {
111
+ checkSame(m, c.amountOff, "apply a coupon in");
112
+ off += BigInt(c.amountOff.amount);
113
+ } else percents = addDecimal(percents, couponPercent(c.percentOff, "percentOff"));
114
+ const cut = percentOfUnits(amount, percents, "half-up") + off;
115
+ return boundedMoney(amount > cut ? amount - cut : 0n, info);
116
+ }
117
+ for (const c of coupons) {
118
+ if ("amountOff" in c) {
119
+ checkSame(m, c.amountOff, "apply a coupon in");
120
+ amount -= BigInt(c.amountOff.amount);
121
+ } else amount -= percentCut(amount, c.percentOff);
122
+ if (amount < 0n) amount = 0n;
123
+ }
124
+ return boundedMoney(amount, info);
125
+ }
126
+ /**
127
+ * The line amount that is still `m` after the subscription's percent coupons take their cut
128
+ * (Stripe discounts a negative invoice line too). A verbatim port of orch `grossUpCredit`,
129
+ * float arithmetic included, because a credit already granted must not change by a cent:
130
+ * `Math.round(m / prod(1 - p / 100))` with the product taken in float64, so a tie is resolved
131
+ * by the float product (9 cents at [10, 20] gives 12, since 0.9 x 0.8 is 0.7200000000000001);
132
+ * when the kept share is not positive (100% off), `m` unchanged.
133
+ * It inverts `applyCoupons` exactly for one percent coupon. For stacked percents it can miss
134
+ * by a cent (about 4% of amounts at [10, 20] or [30, 15]): Stripe rounds each coupon's cut,
135
+ * while this divides by the unrounded product (11 cents at [10, 20] grosses up to 15, which
136
+ * the coupons take to 10).
137
+ */
138
+ function grossUpForPercentOffs(m, percents) {
139
+ const { info } = storedDecimal(m);
140
+ const kept = percents.reduce((share, value) => {
141
+ return share * (1 - Number(formatDecimal(decimalOf(value, true, "percent"))) / 100);
142
+ }, 1);
143
+ if (!(kept > 0)) return m;
144
+ return boundedMoney(BigInt(Math.round(m.amount / kept)), info);
145
+ }
146
+ //#endregion
147
+ //#region src/billing/usage.ts
148
+ const USD_DIGITS = 6;
149
+ /**
150
+ * The exact sum of `quantity x rate x multiplier` over `lines`, rounded half-up once to 6
151
+ * decimals. Replaces the token-cost formulas (`computeCost`, the unpriced-model fallback,
152
+ * the cloner's `estimateCost`). Each line needs exactly one of `usdPerMillion` / `usdPerUnit`.
153
+ */
154
+ function usdCost(lines) {
155
+ let total = {
156
+ units: 0n,
157
+ scale: 0
158
+ };
159
+ for (let i = 0; i < lines.length; i++) {
160
+ const line = lines[i];
161
+ const perMillion = line?.usdPerMillion !== void 0;
162
+ if (perMillion === (line?.usdPerUnit !== void 0)) throw new MoneyError("amount_invalid", `lines[${i}] needs exactly one of usdPerMillion and usdPerUnit, got ${describeValue(line)}`);
163
+ const quantity = decimalOf(line.quantity, true, `lines[${i}].quantity`);
164
+ const rate = decimalOf(perMillion ? line.usdPerMillion : line.usdPerUnit, true, `lines[${i}].rate`);
165
+ const multiplier = decimalOf(line.multiplier ?? 1, true, `lines[${i}].multiplier`);
166
+ const cost = multiplyDecimal(multiplyDecimal(quantity, rate), multiplier);
167
+ total = addDecimal(total, perMillion ? {
168
+ units: cost.units,
169
+ scale: cost.scale + 6
170
+ } : cost);
171
+ }
172
+ return toDecimalText(roundToScale(total, USD_DIGITS, "half-up"));
173
+ }
174
+ //#endregion
175
+ export { annualPrice, applyCoupons, bucketCredits, creditsToUsd, grossUpForPercentOffs, monthlyEquivalent, prorateCredits, usdCost, usdToCredits };