@amboras-dev/money 0.2.0 → 0.3.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.
- package/README.md +15 -8
- package/dist/ai.cjs +12 -13
- package/dist/ai.d.cts +5 -9
- package/dist/ai.d.ts +5 -9
- package/dist/ai.js +12 -13
- package/dist/{allocate-CZRg4TOP.cjs → allocate-AusholrC.cjs} +3 -4
- package/dist/{allocate-X7_jAtOj.js → allocate-BBRfdaSC.js} +3 -4
- package/dist/{amount-BV0O-aN8.cjs → amount-CwFzcxUl.cjs} +29 -12
- package/dist/{amount-BoYBMgK9.js → amount-DHFOK2Yd.js} +29 -12
- package/dist/{arithmetic-BhNwisjb.js → arithmetic-C97-YafD.js} +4 -5
- package/dist/{arithmetic-DjT4Kzgt.cjs → arithmetic-D8A6IeZ0.cjs} +4 -5
- package/dist/billing.cjs +5 -8
- package/dist/billing.d.cts +1 -9
- package/dist/billing.d.ts +1 -9
- package/dist/billing.js +5 -8
- package/dist/commerce.cjs +15 -23
- package/dist/commerce.d.cts +2 -16
- package/dist/commerce.d.ts +2 -16
- package/dist/commerce.js +15 -23
- package/dist/{format-DLhno7eX.cjs → format-92fzvTXY.cjs} +9 -9
- package/dist/{format-BdPnijvn.js → format-BJC513u4.js} +9 -9
- package/dist/fx.cjs +12 -18
- package/dist/fx.d.cts +5 -15
- package/dist/fx.d.ts +5 -15
- package/dist/fx.js +12 -18
- package/dist/index.cjs +10 -12
- package/dist/index.d.cts +17 -23
- package/dist/index.d.ts +17 -23
- package/dist/index.js +10 -12
- package/dist/input.cjs +7 -10
- package/dist/input.d.cts +9 -16
- package/dist/input.d.ts +9 -16
- package/dist/input.js +7 -10
- package/dist/{iso-minor-wSwJN57T.cjs → iso-minor-DyNjT9A1.cjs} +2 -3
- package/dist/{iso-minor-CqkWDxHS.js → iso-minor-odkY-e6I.js} +2 -3
- package/dist/{medusa-lRY9xlsf.cjs → medusa-CzVK0bth.cjs} +3 -4
- package/dist/{medusa-DBXJXXvF.d.ts → medusa-DofdvIgQ.d.cts} +1 -3
- package/dist/{medusa-4K8N1pUc.d.cts → medusa-DrbYmD2Y.d.ts} +1 -3
- package/dist/{medusa-DqJY1qpj.js → medusa-pKGlImn9.js} +3 -4
- package/dist/{money-B-3aFjNJ.d.cts → money-BkMeYDEW.d.cts} +0 -6
- package/dist/{money-B-3aFjNJ.d.ts → money-BkMeYDEW.d.ts} +0 -6
- package/dist/{money-WhnGiI00.js → money-BxbAw7BP.js} +7 -15
- package/dist/{money-B3wH_Z4S.cjs → money-DWBjuuEK.cjs} +7 -15
- package/dist/{number-text-DE-q4jO5.js → number-text-Bj-gZXiF.js} +3 -4
- package/dist/{number-text-BWu67jUq.cjs → number-text-CWuVwrlg.cjs} +3 -4
- package/dist/{own-oP9DXFWv.cjs → own-C5imDkyB.cjs} +1 -2
- package/dist/{own-D7544D3k.js → own-DHxqIYqL.js} +1 -2
- package/dist/{percent-D1VB9xXG.js → percent-CB3Ixjc_.js} +2 -3
- package/dist/{percent-C0InC2t2.cjs → percent-DABHiSBs.cjs} +2 -3
- package/dist/{rate-DBPiYgT1.d.ts → rate-BvvZUdSu.d.ts} +1 -3
- package/dist/{rate-tqK8ijG0.d.cts → rate-CEakW3ia.d.cts} +1 -3
- package/dist/{rate-CVeMii4a.cjs → rate-CEjWTATc.cjs} +3 -4
- package/dist/{rate-DicStBue.js → rate-D6S2_wgZ.js} +3 -4
- package/dist/{reporting-tpGJbqYp.d.ts → reporting-DFNyDBdi.d.cts} +2 -4
- package/dist/{reporting-CG-ai2sM.d.cts → reporting-jIBILvnr.d.ts} +2 -4
- package/dist/{retail-DRUTTQoJ.d.ts → retail-B04CjW3b.d.ts} +1 -3
- package/dist/{retail-Cml8YSKg.cjs → retail-CYGPU4ea.cjs} +4 -5
- package/dist/{retail-C_BPLvMj.d.cts → retail-CmEyJQOH.d.cts} +1 -3
- package/dist/{retail-1mu888J1.js → retail-DXgs11ov.js} +4 -5
- package/dist/{rounding-DHBtNCHG.cjs → rounding-4QKTRbMQ.cjs} +2 -3
- package/dist/{rounding-g9rvye0k.js → rounding-CA1PVP3b.js} +2 -3
- package/dist/sql.cjs +9 -15
- package/dist/sql.d.cts +2 -12
- package/dist/sql.d.ts +2 -12
- package/dist/sql.js +9 -15
- package/dist/testing.cjs +39198 -12
- package/dist/testing.d.cts +20 -13
- package/dist/testing.d.ts +20 -13
- package/dist/testing.js +39198 -13
- package/dist/{wire-sW0yzqth.js → wire-B4QU8O3W.js} +350 -93
- package/dist/{wire-CaUN9Vf_.d.ts → wire-CVNicirK.d.ts} +165 -46
- package/dist/{wire-xjYXv4go.d.cts → wire-CtNJmWVP.d.cts} +165 -46
- package/dist/{wire-Bhuut8cW.cjs → wire-u2tmHY0Y.cjs} +403 -92
- package/dist/wire.cjs +10 -1
- package/dist/wire.d.cts +2 -2
- package/dist/wire.d.ts +2 -2
- package/dist/wire.js +1 -2
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -152,7 +152,9 @@ unit apart).
|
|
|
152
152
|
**Formatting** (for people, not byte-stable across ICU): `formatMoney` (display digits, or
|
|
153
153
|
`widen` / `trim-zero`; explicit locale, default `en-US`), `formatMoneyCompact`,
|
|
154
154
|
`formatMoneyRange`, `moneyAxisFormatter`, `formatStored` (a possibly fractional stored amount,
|
|
155
|
-
rounded half-up to a storage unit, then `formatMoney`).
|
|
155
|
+
rounded half-up to a storage unit, then `formatMoney`). `rounding: 'down'` rounds toward zero
|
|
156
|
+
instead, for an upper bound that must never read above itself (a refund cap of HUF 435050 shows
|
|
157
|
+
`HUF 4,350`, not 4,351).
|
|
156
158
|
|
|
157
159
|
Internals other entry points build on (never exported), each the only implementation of its concept:
|
|
158
160
|
`internal/number-text.ts` `readNumberText` (the one separator engine), `internal/rational.ts`
|
|
@@ -168,18 +170,20 @@ import { money } from '@amboras-dev/money'
|
|
|
168
170
|
import { ISO_MINOR_V2, conventionOf, decodeWire, encodeWire } from '@amboras-dev/money/wire'
|
|
169
171
|
|
|
170
172
|
encodeWire('stripe', money(120000, 'JPY'), { convention: ISO_MINOR_V2 }) // 1200
|
|
171
|
-
decodeWire('stripe', 1200, 'JPY', conventionOf(paymentIntent))
|
|
173
|
+
decodeWire('stripe', 1200, 'JPY', { convention: conventionOf(paymentIntent) }) // { amount: 120000, currency: 'JPY' }
|
|
174
|
+
decodeWire('stripe', -44, 'USD', { convention: ISO_MINOR_V2, allowNegative: true }) // signed balance read
|
|
172
175
|
encodeWire('mollie', { amount: '107.8175', currency: 'USD' }) // '1.08': a fractional stored total
|
|
173
176
|
```
|
|
174
177
|
|
|
175
178
|
A Medusa session amount can be fractional at storage scale (107.8175). `encodeWire` and `sameWireAmount` take it as a `FractionalMoney` and round it by the PSP's own rule: Stripe and Adyen to a whole storage unit first, whole-unit PSPs (MoMo, Payoo, VNPay, Paystack XOF) refuse it, a pre-cutover Razorpay payment refuses it, every other PSP rounds once, half-up, at its wire unit.
|
|
176
179
|
|
|
177
|
-
- Codec: `encodeWire`, `decodeWire
|
|
180
|
+
- Codec: `encodeWire`, `decodeWire` (one options object `{ convention, allowNegative }`; a negative provider amount throws unless `allowNegative: true`), `tryDecodeWire` (never throws), `wireFactor`, `pspWireDigits`, `oneWireUnit`, `wireBreakdown` (line items that sum to the encoded total), `wireKeyPart`.
|
|
178
181
|
- Cutover marker: `WIRE_SCALE_KEY`, `WIRE_SCALE_ISO_MINOR_V2`, `WIRE_SCALE_MARKER`, `ISO_MINOR_V2`, `PRE_CUTOVER`, `conventionOf` (gated PSPs need the convention; there is no hidden default).
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
-
|
|
182
|
-
-
|
|
182
|
+
- Wire-rounding marker: `WIRE_ROUNDING_KEY`, `WIRE_ROUNDING_EXACT`, `WIRE_ROUNDING_MARKER` (stamped on new sessions of the PSPs Medusa encoded in floating point before 0.2.0), `mayHoldLegacyFloatRounding` (no marker: the session predates that deploy).
|
|
183
|
+
- Provider tables: `PSP_IDS`, `pspSupportsCurrency`, `assertPspCurrency` (typed `PspCurrency<P>`: `AuthorizeNetCurrency`, `IyzicoCurrency`, `PaystackCurrency`, `'VND'` for the short fixed lists; `PAYSTACK_CURRENCIES` is Paystack's list as a tuple for validators and pickers), `pspCurrencies` (what the codec scales), `pspDefaultCurrency` (single-currency PSPs only), `pspHomeCurrency` (the currency every account charges without enabling anything: Razorpay INR, iyzico TRY, the VND rails), `pspHomeCountry` (the one country every account is registered in: iyzico TR, the VND rails VN), `pspMinimumAmount`.
|
|
184
|
+
- Presentment (eligibility): `pspPresentmentCurrencies(psp, { accountCountry, includeOptIn })` (what an account can charge buyers in), `pspPresentment(psp)` (the rules as data: union, currencies needing an account country, account countries limited to a list, Paystack's per-country lists with USD as an explicit opt-in, and `optInCompanions`), `pspAccountCountryForCurrency` (Paystack NGN names NG; USD names nothing).
|
|
185
|
+
- Comparison: `sameWireAmount` (a webhook amount against the expected Money or FractionalMoney, exactly at storage scale; `acceptLegacyIsoDigits`, `textOnly`, and `acceptLegacyFloatRounding` for the spelling Medusa's pre-0.2.0 float codec sent PayPal, Mollie, Airwallex, iyzico, Authorize.Net and Payoo: USD 1072.5 went as `'10.72'`, 0.2.0 sends `'10.73'`; pass `mayHoldLegacyFloatRounding(sessionData)`), and `legacyFloatRefundClamp` (a refund of such a capture lowered to what the PSP still holds, after any partial refunds: `'10.00'` where Medusa's remaining encodes `'10.01'`).
|
|
186
|
+
- Provider quirks: `verificationCharge` (currency optional for Razorpay, INR only, and Mollie, EUR fallback), `RAZORPAY_AFA_FREE_CAP`, `iyzicoPrice`, `authorizeNetReversibleAmount`, `jsonDecimalLiteral`.
|
|
183
187
|
- Provider text: `parseProviderAmount` (carrier quotes, dispute amounts; `decimalSeparator: 'auto'` is the `/input` rule).
|
|
184
188
|
- Destinations: `destinationAmount` (pixels, ad platforms, email tools), `feedPrice`, `googleMicros`, `eventMoney` (ClickHouse `Int64` contract).
|
|
185
189
|
- Plugin slots: `slotMoney`, `readSlotMoney`.
|
|
@@ -260,6 +264,7 @@ inputStep('KWD') // '0.001'
|
|
|
260
264
|
- `parseAmountInput(text, currency, { locale, decimalSeparator, allowNegative, allowZero, min, max, original })`: strips the currency's symbols and code and refuses more fraction digits than the currency stores.
|
|
261
265
|
Without `locale` (the chat wizard and any field that has none) it reads the `'auto'` rule below, which is today's admin `parseMajorUnits` except the listed differences, and a currency storing 0 digits rounds a fraction half-up to a whole unit (`'1200.5'` JPY is 1201), as admin does.
|
|
262
266
|
With `locale` it reads that locale's separators: the group separator is valid only in thousands groups (`'1,234,567'`) or Indian lakh groups (`'12,34,567'`: three digits on the right, then pairs), and only when every group fits (`'1,2345'` and `'12,3,456'` are `unparseable`), and extra digits are `too_many_decimals` in every currency.
|
|
267
|
+
The locale's decimal separator is never read as grouping: in a currency storing 0 digits, en-US `'1.234'`, `'19.999'` and `'1.000'` JPY are `too_many_decimals` (a thousands group to some, a fraction to others, and trailing zeros would let `'1.000'` through as ¥1); grouping uses the locale's group separator (`'1,234'` en-US, `'1.234'` de-DE).
|
|
263
268
|
**Every number input (`<input type="number">`) must pass `{ decimalSeparator: '.' }`**: browsers always serialise its value with a `.` decimal and no grouping, so the free-text rule would misread it (TND and IQD `'1.500'` are 1,500 there, 1.5 here).
|
|
264
269
|
In `'.'` mode `.` is the decimal, `,` and grouping are `unparseable`, a zero whole part may be omitted (`'.5'` is 0.5), and a currency storing 0 digits rounds a fraction half-up to a whole unit, as admin `numericEntryToMinorUnits` does (`'1200.5'` JPY is 1201).
|
|
265
270
|
It equals that function on every `.`-only value within the storage digits (`test/input/input.test.ts`), except one class:
|
|
@@ -270,7 +275,7 @@ inputStep('KWD') // '0.001'
|
|
|
270
275
|
Two amounts have no such spelling and read back only through `original`: IQD with a three-digit fraction (`'1.125'` reads as 1,125, since IQD displays no decimals) and a sub-unit amount of a currency storing 0 digits (`'1200.5'` JPY reads as a whole 1,201).
|
|
271
276
|
With `locale` it uses that locale's separators and the storage digits (`'12,50'` de-DE); `digits: 'storage'` or `'trim-zero'` overrides the zeros either way.
|
|
272
277
|
With `decimalSeparator: '.'` (a number input's prefill) it prints `.` as the decimal, never groups and drops trailing zeros (TND 1500 is `'1.5'`), so `parseAmountInput(text, currency, { decimalSeparator: '.' })` reads it back exactly (a sub-unit JPY amount through `original`).
|
|
273
|
-
Prefill and save with `parseAmountInput(text, currency, { original: m })`: the untouched prefill text, or any text equal in value to `m`, returns `m` unchanged, so saving an untouched field never rewrites the amount.
|
|
278
|
+
Prefill and save with `parseAmountInput(text, currency, { original: m })`: the untouched prefill text, or any text equal in value to `m`, returns `m` unchanged, so saving an untouched field never rewrites the amount (an ambiguous zero-digit text such as en-US `'1.000'` JPY is refused even then).
|
|
274
279
|
- `inputStep`, `inputPlaceholder`: from the storage digits, never a hardcoded `0.01`.
|
|
275
280
|
- `parseImportAmount(text, { decimalSeparator: 'auto' | '.' | ',', currency, allowNegative, allowTrailingText })`: CSV, supplier, scraped and carrier text to `DecimalText` (major units), or `null`.
|
|
276
281
|
`'auto'` is the only comma/dot rule in the package (`/ai` `moneyMentions`, `/wire` `parseProviderAmount` and `moneyTemplateOf` use it too), and it treats `.` and `,` the same way:
|
|
@@ -318,6 +323,7 @@ Framework-agnostic: no test runner or fast-check dependency.
|
|
|
318
323
|
A positive amount whose wire rounds to 0 (68 vectors: one storage unit of a zero-decimal currency at Stripe, Adyen, Razorpay, Airwallex, PayPal HUF/JPY/TWD, Mollie ISK/JPY) is a refusal vector by package policy: `encodeWire` refuses it with `wire_refused` (`... rounds to 0 at <Tag> ...`).
|
|
319
324
|
Medusa today puts 0 on the wire at initiate for Stripe and Adyen, and for Razorpay, PayPal and Airwallex everywhere; it refuses at refund and capture for Stripe and Adyen, and in Mollie, iyzico and Authorize.Net.
|
|
320
325
|
- `PSP_FRACTIONAL_GOLDEN_VECTORS`: the same codecs fed fractional storage amounts (0.4, 0.5, ties, 107.8175, a fraction of a dong); `policy` and `medusa` mark where `encodeWire` departs from them on purpose (the sub-unit refusal, exact half-up at ties float `toFixed` rounds down, the real amount in a refusal message).
|
|
326
|
+
- `PSP_LEGACY_FLOAT_GOLDEN_VECTORS`: for the PSPs Medusa encoded in floating point before 0.2.0, what those codecs sent at whole, fractional and tie amounts, and their ISO-digit spelling; `sameWireAmount`'s `acceptLegacyFloatRounding` accepts every one.
|
|
321
327
|
- `DESTINATION_GOLDEN_VECTORS`: what `destinationAmount`, `feedPrice` and `googleMicros` must give per destination.
|
|
322
328
|
- `checkWireDigits(psp, documented)`: the capability-consistency probe; returns mismatch messages.
|
|
323
329
|
- `normalizeIntlSpaces(s)`, `sampleMoney(currency, major)`.
|
|
@@ -339,6 +345,7 @@ The full list, with the decision behind each, is in [`docs/money/API-SPEC.md`](.
|
|
|
339
345
|
`formatMoney`, `moneyView` (`/ai`), `destinationAmount` and `feedPrice` (`/wire`) use the display digits; capability-kit, the plugins and Medusa's `formatMoney` use the storage digits today.
|
|
340
346
|
This is deliberate: it is what the PSPs charge and what people read.
|
|
341
347
|
- `formatMoney` (default `digits: 'display'`) rounds half-up: HUF 435050 was `HUF 4,350.50` and is `HUF 4,351`; IQD 1500 was `IQD 1.500` and is `IQD 2`.
|
|
348
|
+
A cap ("refund at most") passes `rounding: 'down'`, so HUF 435050 shows `HUF 4,350` and typing the amount shown stays within it.
|
|
342
349
|
- Catalog feeds and destinations drop the sub-unit the same way: `feedPrice(money(435050, 'HUF'))` is `'4351 HUF'`, IQD 1500 is `'2 IQD'`.
|
|
343
350
|
- Model views keep the value but show fewer zeros: `moneyView` returns the exact `amount` and widens `display` only for a sub-unit value (`'HUF 4,350.5'`), so HUF 435000 is `'HUF 4,350'` where capability-kit shows `'HUF 4,350.00'`, and IQD 1500 is `'IQD 1.5'` where it shows `'IQD 1.500'`.
|
|
344
351
|
- Machine files that need storage precision use `toMajorDecimal(m)` or `formatAmountInput(m)`.
|
package/dist/ai.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_money = require("./money-
|
|
3
|
-
const require_format = require("./format-
|
|
4
|
-
const require_number_text = require("./number-text-
|
|
5
|
-
const require_amount = require("./amount-
|
|
6
|
-
|
|
2
|
+
const require_money = require("./money-DWBjuuEK.cjs");
|
|
3
|
+
const require_format = require("./format-92fzvTXY.cjs");
|
|
4
|
+
const require_number_text = require("./number-text-CWuVwrlg.cjs");
|
|
5
|
+
const require_amount = require("./amount-CwFzcxUl.cjs");
|
|
6
|
+
// src/ai/mentions.ts
|
|
7
7
|
const ACTIVE = require_money.CURRENCIES.filter((c) => c.status === "active");
|
|
8
8
|
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
9
9
|
const LETTER_START = /^\p{L}/u;
|
|
@@ -84,8 +84,7 @@ function moneyMentions(text, options = {}) {
|
|
|
84
84
|
}
|
|
85
85
|
return mentions;
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
//#region src/ai/wording.ts
|
|
87
|
+
// src/ai/wording.ts
|
|
89
88
|
/** The one unit sentence every money field carries, for the model and for people. Verbatim. */
|
|
90
89
|
const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
|
|
91
90
|
/** JSON Schema keyword marking a money field. Verbatim. */
|
|
@@ -99,8 +98,7 @@ function moneyFieldDescription(describe) {
|
|
|
99
98
|
const lead = typeof describe === "string" ? describe.trim() : "";
|
|
100
99
|
return lead ? `${lead} ${MONEY_UNIT_SENTENCE}` : MONEY_UNIT_SENTENCE;
|
|
101
100
|
}
|
|
102
|
-
|
|
103
|
-
//#region src/ai/model-money.ts
|
|
101
|
+
// src/ai/model-money.ts
|
|
104
102
|
/**
|
|
105
103
|
* `{ amount: 19.99, currency: 'USD', display: '$19.99' }`. `amount` is exact; `display` shows the
|
|
106
104
|
* display digits, widened to keep a stored value finer than them (JPY 550 is `'¥5.5'`, HUF 435050
|
|
@@ -126,7 +124,7 @@ function failure(reason, input, code, digits) {
|
|
|
126
124
|
error: `${{
|
|
127
125
|
empty: `Not an amount: ${shown}.`,
|
|
128
126
|
unparseable: `Not an amount: ${shown}.`,
|
|
129
|
-
too_many_decimals: `${shown} has more decimal places than ${code} uses (at most ${digits}).`,
|
|
127
|
+
too_many_decimals: digits === 0 ? `${shown} has more decimal places than ${code} uses (at most 0). Write thousands with ',' or without a separator (1,500 or 1500).` : `${shown} has more decimal places than ${code} uses (at most ${digits}).`,
|
|
130
128
|
negative: "The amount must not be negative.",
|
|
131
129
|
zero: "The amount must be more than zero.",
|
|
132
130
|
below_min: `${shown} is out of range for ${code}.`,
|
|
@@ -148,8 +146,10 @@ function fromNumber(input, currency, options) {
|
|
|
148
146
|
* An amount from a model tool call (major units, as the merchant says it) as Money, or an
|
|
149
147
|
* error to send back to the model ending with `MONEY_UNIT_SENTENCE`. Numbers go through
|
|
150
148
|
* `fromMajor` (float noise such as 0.1 + 0.2 allowed); strings through `/input`'s entry rule
|
|
151
|
-
* in English (`'1,299.00'`, `'$1,299'`, `'99
|
|
152
|
-
*
|
|
149
|
+
* in English (`'1,299.00'`, `'$1,299'`, `'99,000'` VND). More fraction digits than the
|
|
150
|
+
* currency stores and negative amounts are refused, never rounded or flipped; so is a `.` before
|
|
151
|
+
* three digits in a currency without decimals (`'99.000'` VND, `'1.500'` JPY: a thousands group
|
|
152
|
+
* or a fraction?), and the error asks the model for `1,500` or `1500`. An
|
|
153
153
|
* unknown currency throws `currency_invalid` (it is the tool's, not the model's).
|
|
154
154
|
*/
|
|
155
155
|
function parseModelMoney(input, currency, options = {}) {
|
|
@@ -172,7 +172,6 @@ function parseModelMoney(input, currency, options = {}) {
|
|
|
172
172
|
money: result.money
|
|
173
173
|
} : failure(result.reason, input, info.code, info.storageDigits);
|
|
174
174
|
}
|
|
175
|
-
//#endregion
|
|
176
175
|
exports.MONEY_META_KEY = MONEY_META_KEY;
|
|
177
176
|
exports.MONEY_UNIT_SENTENCE = MONEY_UNIT_SENTENCE;
|
|
178
177
|
exports.RATE_UNIT_SENTENCE = RATE_UNIT_SENTENCE;
|
package/dist/ai.d.cts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { R as DecimalText, g as CurrencyCode, i as Money } from "./money-
|
|
2
|
-
//#region src/ai/mentions.d.ts
|
|
1
|
+
import { R as DecimalText, g as CurrencyCode, i as Money } from "./money-BkMeYDEW.cjs";
|
|
3
2
|
/** One price found in text. `code` is set when the mark names one currency; `amount` when the number reads (see the separator engine's 'auto' rule). */
|
|
4
3
|
interface MoneyMention {
|
|
5
4
|
/** The matched text, mark and number. */
|
|
@@ -43,8 +42,6 @@ export declare function moneyLiteralPattern(options?: {
|
|
|
43
42
|
export declare function moneyMentions(text: string, options?: {
|
|
44
43
|
currency?: string;
|
|
45
44
|
}): MoneyMention[];
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/ai/model-money.d.ts
|
|
48
45
|
/** A stored amount as the model sees it: the major amount, the code and a display string. */
|
|
49
46
|
interface MoneyView {
|
|
50
47
|
readonly amount: number;
|
|
@@ -76,13 +73,13 @@ interface ParseModelMoneyOptions {
|
|
|
76
73
|
* An amount from a model tool call (major units, as the merchant says it) as Money, or an
|
|
77
74
|
* error to send back to the model ending with `MONEY_UNIT_SENTENCE`. Numbers go through
|
|
78
75
|
* `fromMajor` (float noise such as 0.1 + 0.2 allowed); strings through `/input`'s entry rule
|
|
79
|
-
* in English (`'1,299.00'`, `'$1,299'`, `'99
|
|
80
|
-
*
|
|
76
|
+
* in English (`'1,299.00'`, `'$1,299'`, `'99,000'` VND). More fraction digits than the
|
|
77
|
+
* currency stores and negative amounts are refused, never rounded or flipped; so is a `.` before
|
|
78
|
+
* three digits in a currency without decimals (`'99.000'` VND, `'1.500'` JPY: a thousands group
|
|
79
|
+
* or a fraction?), and the error asks the model for `1,500` or `1500`. An
|
|
81
80
|
* unknown currency throws `currency_invalid` (it is the tool's, not the model's).
|
|
82
81
|
*/
|
|
83
82
|
export declare function parseModelMoney(input: unknown, currency: string, options?: ParseModelMoneyOptions): ParseModelMoneyResult;
|
|
84
|
-
//#endregion
|
|
85
|
-
//#region src/ai/wording.d.ts
|
|
86
83
|
/** The one unit sentence every money field carries, for the model and for people. Verbatim. */
|
|
87
84
|
export declare const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
|
|
88
85
|
/** JSON Schema keyword marking a money field. Verbatim. */
|
|
@@ -93,5 +90,4 @@ export declare const STORAGE_UNIT_SENTENCE = "Store API amounts are storage unit
|
|
|
93
90
|
export declare const RATE_UNIT_SENTENCE = "1 unit of the store currency equals this many units of the other currency.";
|
|
94
91
|
/** A money field's description: `describe` (trimmed) followed by the unit sentence, or the sentence alone. */
|
|
95
92
|
export declare function moneyFieldDescription(describe?: string): string;
|
|
96
|
-
//#endregion
|
|
97
93
|
export type { MoneyMention, MoneyView, ParseModelMoneyOptions, ParseModelMoneyResult };
|
package/dist/ai.d.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { R as DecimalText, g as CurrencyCode, i as Money } from "./money-
|
|
2
|
-
//#region src/ai/mentions.d.ts
|
|
1
|
+
import { R as DecimalText, g as CurrencyCode, i as Money } from "./money-BkMeYDEW.js";
|
|
3
2
|
/** One price found in text. `code` is set when the mark names one currency; `amount` when the number reads (see the separator engine's 'auto' rule). */
|
|
4
3
|
interface MoneyMention {
|
|
5
4
|
/** The matched text, mark and number. */
|
|
@@ -43,8 +42,6 @@ export declare function moneyLiteralPattern(options?: {
|
|
|
43
42
|
export declare function moneyMentions(text: string, options?: {
|
|
44
43
|
currency?: string;
|
|
45
44
|
}): MoneyMention[];
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/ai/model-money.d.ts
|
|
48
45
|
/** A stored amount as the model sees it: the major amount, the code and a display string. */
|
|
49
46
|
interface MoneyView {
|
|
50
47
|
readonly amount: number;
|
|
@@ -76,13 +73,13 @@ interface ParseModelMoneyOptions {
|
|
|
76
73
|
* An amount from a model tool call (major units, as the merchant says it) as Money, or an
|
|
77
74
|
* error to send back to the model ending with `MONEY_UNIT_SENTENCE`. Numbers go through
|
|
78
75
|
* `fromMajor` (float noise such as 0.1 + 0.2 allowed); strings through `/input`'s entry rule
|
|
79
|
-
* in English (`'1,299.00'`, `'$1,299'`, `'99
|
|
80
|
-
*
|
|
76
|
+
* in English (`'1,299.00'`, `'$1,299'`, `'99,000'` VND). More fraction digits than the
|
|
77
|
+
* currency stores and negative amounts are refused, never rounded or flipped; so is a `.` before
|
|
78
|
+
* three digits in a currency without decimals (`'99.000'` VND, `'1.500'` JPY: a thousands group
|
|
79
|
+
* or a fraction?), and the error asks the model for `1,500` or `1500`. An
|
|
81
80
|
* unknown currency throws `currency_invalid` (it is the tool's, not the model's).
|
|
82
81
|
*/
|
|
83
82
|
export declare function parseModelMoney(input: unknown, currency: string, options?: ParseModelMoneyOptions): ParseModelMoneyResult;
|
|
84
|
-
//#endregion
|
|
85
|
-
//#region src/ai/wording.d.ts
|
|
86
83
|
/** The one unit sentence every money field carries, for the model and for people. Verbatim. */
|
|
87
84
|
export declare const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
|
|
88
85
|
/** JSON Schema keyword marking a money field. Verbatim. */
|
|
@@ -93,5 +90,4 @@ export declare const STORAGE_UNIT_SENTENCE = "Store API amounts are storage unit
|
|
|
93
90
|
export declare const RATE_UNIT_SENTENCE = "1 unit of the store currency equals this many units of the other currency.";
|
|
94
91
|
/** A money field's description: `describe` (trimmed) followed by the unit sentence, or the sentence alone. */
|
|
95
92
|
export declare function moneyFieldDescription(describe?: string): string;
|
|
96
|
-
//#endregion
|
|
97
93
|
export type { MoneyMention, MoneyView, ParseModelMoneyOptions, ParseModelMoneyResult };
|
package/dist/ai.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { $ as describeValue, E as resolveCurrency, Q as MoneyError, l as toMajorNumber, m as currencyMarks, n as fromMajor, pt as toDecimalText, s as storedDecimal, u as CURRENCIES } from "./money-
|
|
2
|
-
import { t as formatMoney } from "./format-
|
|
3
|
-
import { n as readNumberText, t as digitGroupingSource } from "./number-text-
|
|
4
|
-
import { s as parseAmountInput } from "./amount-
|
|
5
|
-
|
|
1
|
+
import { $ as describeValue, E as resolveCurrency, Q as MoneyError, l as toMajorNumber, m as currencyMarks, n as fromMajor, pt as toDecimalText, s as storedDecimal, u as CURRENCIES } from "./money-BxbAw7BP.js";
|
|
2
|
+
import { t as formatMoney } from "./format-BJC513u4.js";
|
|
3
|
+
import { n as readNumberText, t as digitGroupingSource } from "./number-text-Bj-gZXiF.js";
|
|
4
|
+
import { s as parseAmountInput } from "./amount-DHFOK2Yd.js";
|
|
5
|
+
// src/ai/mentions.ts
|
|
6
6
|
const ACTIVE = CURRENCIES.filter((c) => c.status === "active");
|
|
7
7
|
const escapeRegExp = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
8
8
|
const LETTER_START = /^\p{L}/u;
|
|
@@ -83,8 +83,7 @@ function moneyMentions(text, options = {}) {
|
|
|
83
83
|
}
|
|
84
84
|
return mentions;
|
|
85
85
|
}
|
|
86
|
-
|
|
87
|
-
//#region src/ai/wording.ts
|
|
86
|
+
// src/ai/wording.ts
|
|
88
87
|
/** The one unit sentence every money field carries, for the model and for people. Verbatim. */
|
|
89
88
|
const MONEY_UNIT_SENTENCE = "Amount in normal currency units as the merchant says it (99 means 99.00; 1.5 means 1.50). Never cents or minor units.";
|
|
90
89
|
/** JSON Schema keyword marking a money field. Verbatim. */
|
|
@@ -98,8 +97,7 @@ function moneyFieldDescription(describe) {
|
|
|
98
97
|
const lead = typeof describe === "string" ? describe.trim() : "";
|
|
99
98
|
return lead ? `${lead} ${MONEY_UNIT_SENTENCE}` : MONEY_UNIT_SENTENCE;
|
|
100
99
|
}
|
|
101
|
-
|
|
102
|
-
//#region src/ai/model-money.ts
|
|
100
|
+
// src/ai/model-money.ts
|
|
103
101
|
/**
|
|
104
102
|
* `{ amount: 19.99, currency: 'USD', display: '$19.99' }`. `amount` is exact; `display` shows the
|
|
105
103
|
* display digits, widened to keep a stored value finer than them (JPY 550 is `'¥5.5'`, HUF 435050
|
|
@@ -125,7 +123,7 @@ function failure(reason, input, code, digits) {
|
|
|
125
123
|
error: `${{
|
|
126
124
|
empty: `Not an amount: ${shown}.`,
|
|
127
125
|
unparseable: `Not an amount: ${shown}.`,
|
|
128
|
-
too_many_decimals: `${shown} has more decimal places than ${code} uses (at most ${digits}).`,
|
|
126
|
+
too_many_decimals: digits === 0 ? `${shown} has more decimal places than ${code} uses (at most 0). Write thousands with ',' or without a separator (1,500 or 1500).` : `${shown} has more decimal places than ${code} uses (at most ${digits}).`,
|
|
129
127
|
negative: "The amount must not be negative.",
|
|
130
128
|
zero: "The amount must be more than zero.",
|
|
131
129
|
below_min: `${shown} is out of range for ${code}.`,
|
|
@@ -147,8 +145,10 @@ function fromNumber(input, currency, options) {
|
|
|
147
145
|
* An amount from a model tool call (major units, as the merchant says it) as Money, or an
|
|
148
146
|
* error to send back to the model ending with `MONEY_UNIT_SENTENCE`. Numbers go through
|
|
149
147
|
* `fromMajor` (float noise such as 0.1 + 0.2 allowed); strings through `/input`'s entry rule
|
|
150
|
-
* in English (`'1,299.00'`, `'$1,299'`, `'99
|
|
151
|
-
*
|
|
148
|
+
* in English (`'1,299.00'`, `'$1,299'`, `'99,000'` VND). More fraction digits than the
|
|
149
|
+
* currency stores and negative amounts are refused, never rounded or flipped; so is a `.` before
|
|
150
|
+
* three digits in a currency without decimals (`'99.000'` VND, `'1.500'` JPY: a thousands group
|
|
151
|
+
* or a fraction?), and the error asks the model for `1,500` or `1500`. An
|
|
152
152
|
* unknown currency throws `currency_invalid` (it is the tool's, not the model's).
|
|
153
153
|
*/
|
|
154
154
|
function parseModelMoney(input, currency, options = {}) {
|
|
@@ -171,5 +171,4 @@ function parseModelMoney(input, currency, options = {}) {
|
|
|
171
171
|
money: result.money
|
|
172
172
|
} : failure(result.reason, input, info.code, info.storageDigits);
|
|
173
173
|
}
|
|
174
|
-
//#endregion
|
|
175
174
|
export { MONEY_META_KEY, MONEY_UNIT_SENTENCE, RATE_UNIT_SENTENCE, STORAGE_UNIT_SENTENCE, currencySymbolAliases, moneyFieldDescription, moneyLiteralPattern, moneyMentions, moneyView, parseModelMoney };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const require_money = require("./money-
|
|
2
|
-
const require_rounding = require("./rounding-
|
|
3
|
-
|
|
1
|
+
const require_money = require("./money-DWBjuuEK.cjs");
|
|
2
|
+
const require_rounding = require("./rounding-4QKTRbMQ.cjs");
|
|
3
|
+
// src/allocate.ts
|
|
4
4
|
const invalid = (message) => new require_money.MoneyError("amount_invalid", message);
|
|
5
5
|
const sumOf = (values) => values.reduce((a, b) => a + b, 0n);
|
|
6
6
|
const floorShares = (units, weights, total) => weights.map((w) => require_money.divRound(units * w, total, "floor"));
|
|
@@ -195,7 +195,6 @@ function splitUnitPrice(net, quantity, options = {}) {
|
|
|
195
195
|
});
|
|
196
196
|
return parts;
|
|
197
197
|
}
|
|
198
|
-
//#endregion
|
|
199
198
|
Object.defineProperty(exports, "allocate", {
|
|
200
199
|
enumerable: true,
|
|
201
200
|
get: function() {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { $ as describeValue, B as decimalOf, Q as MoneyError, X as mulDiv, at as fractionDigitsOf, dt as roundToScale, pt as toDecimalText, rt as divRound, s as storedDecimal, t as boundedMoney } from "./money-
|
|
2
|
-
import { a as unitSize } from "./rounding-
|
|
3
|
-
|
|
1
|
+
import { $ as describeValue, B as decimalOf, Q as MoneyError, X as mulDiv, at as fractionDigitsOf, dt as roundToScale, pt as toDecimalText, rt as divRound, s as storedDecimal, t as boundedMoney } from "./money-BxbAw7BP.js";
|
|
2
|
+
import { a as unitSize } from "./rounding-CA1PVP3b.js";
|
|
3
|
+
// src/allocate.ts
|
|
4
4
|
const invalid = (message) => new MoneyError("amount_invalid", message);
|
|
5
5
|
const sumOf = (values) => values.reduce((a, b) => a + b, 0n);
|
|
6
6
|
const floorShares = (units, weights, total) => weights.map((w) => divRound(units * w, total, "floor"));
|
|
@@ -195,5 +195,4 @@ function splitUnitPrice(net, quantity, options = {}) {
|
|
|
195
195
|
});
|
|
196
196
|
return parts;
|
|
197
197
|
}
|
|
198
|
-
//#endregion
|
|
199
198
|
export { splitUnitPrice as i, allocateUnits as n, allocateWaterfall as r, allocate as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const require_money = require("./money-
|
|
2
|
-
const require_arithmetic = require("./arithmetic-
|
|
3
|
-
const require_number_text = require("./number-text-
|
|
4
|
-
|
|
1
|
+
const require_money = require("./money-DWBjuuEK.cjs");
|
|
2
|
+
const require_arithmetic = require("./arithmetic-D8A6IeZ0.cjs");
|
|
3
|
+
const require_number_text = require("./number-text-CWuVwrlg.cjs");
|
|
4
|
+
// src/input/amount.ts
|
|
5
5
|
/** Internal: the exact signed decimal a successful separator-engine read holds. */
|
|
6
6
|
function decimalOfText(read) {
|
|
7
7
|
return {
|
|
@@ -69,6 +69,15 @@ function echoesOriginal(shown, options) {
|
|
|
69
69
|
grouped: true
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* In a currency storing 0 digits, the locale's decimal separator before exactly three digits
|
|
74
|
+
* after a whole part of at most three (en-US `'1.234'`, `'19.999'`, `'1.000'` JPY) reads as a
|
|
75
|
+
* thousands group to some people and as a fraction to others; trailing zeros would even let
|
|
76
|
+
* `'1.000'` through as 1 yen. Neither reading is safe, so a locale field refuses it.
|
|
77
|
+
*/
|
|
78
|
+
function readsAsGroupOrFraction(read, info) {
|
|
79
|
+
return info.storageDigits === 0 && read.decimalMark !== null && read.scale === 3 && read.units < 1000000n;
|
|
80
|
+
}
|
|
72
81
|
/** Which reading a field's options ask for; `decimalSeparator` and `locale` together, or a `decimalSeparator` other than `'.'`, throw. */
|
|
73
82
|
function entryMode(options) {
|
|
74
83
|
if (options.decimalSeparator === void 0) return options.locale === void 0 ? "auto" : "locale";
|
|
@@ -92,17 +101,18 @@ const ENTRY_SEPARATORS = {
|
|
|
92
101
|
* against `,` (see `parseImportAmount`): `'29,99'` EUR is 29.99, `'1.299,00'` and `'1,299'`
|
|
93
102
|
* are 1299, KWD `'1,5'` is 1.5; a currency storing 0 digits rounds a fraction half-up to a
|
|
94
103
|
* whole unit (`'1200.5'` JPY is 1201).
|
|
95
|
-
* - With `locale`: its decimal separator is the decimal and its group separator is valid
|
|
96
|
-
* 3-digit (or Indian lakh) groups
|
|
97
|
-
* before exactly three digits
|
|
98
|
-
*
|
|
99
|
-
*
|
|
104
|
+
* - With `locale`: its decimal separator is always the decimal and its group separator is valid
|
|
105
|
+
* only in 3-digit (or Indian lakh) groups. In a currency that stores no fraction (JPY, VND) the
|
|
106
|
+
* decimal separator before exactly three digits is ambiguous (en-US `'1.234'` JPY could be
|
|
107
|
+
* 1,234 or 1.234) and is `'too_many_decimals'`, never read as grouping; grouping takes the
|
|
108
|
+
* locale's group separator (`'1,234'` en-US, `'1.234'` de-DE). en-US `'29,99'` USD is
|
|
109
|
+
* `'unparseable'`; de-DE `'29,99'` EUR is 29.99; en-US `'1200.5'` JPY is `'too_many_decimals'`.
|
|
100
110
|
* - With `decimalSeparator: '.'` (every `<input type="number">`): `.` is the decimal, nothing
|
|
101
111
|
* groups (`'1,299'` is `'unparseable'`), TND and IQD `'1.500'` are 1.5, and a currency storing
|
|
102
112
|
* 0 digits rounds a fraction half-up to a whole unit (`'1.5'` JPY is 2).
|
|
103
113
|
* Otherwise more fraction digits than the currency stores is `'too_many_decimals'`, never rounded
|
|
104
114
|
* (`'1,2345'` USD). Text equal to `original` returns it unchanged (JPY 120050 shown as
|
|
105
|
-
* `'1200.50'`). An unknown currency, or an `original` in another currency, throws.
|
|
115
|
+
* `'1200.50'`), except the ambiguous zero-digit text above. An unknown currency, or an `original` in another currency, throws.
|
|
106
116
|
*/
|
|
107
117
|
function parseAmountInput(text, currency, options = {}) {
|
|
108
118
|
const info = require_money.resolveCurrency(currency, { allowHistoric: true });
|
|
@@ -112,7 +122,10 @@ function parseAmountInput(text, currency, options = {}) {
|
|
|
112
122
|
let value;
|
|
113
123
|
if (echoesOriginal(shown, options)) value = require_money.storedDecimal(options.original).value;
|
|
114
124
|
else {
|
|
115
|
-
const separators = mode === "locale" ?
|
|
125
|
+
const separators = mode === "locale" ? {
|
|
126
|
+
...localeSeparators(options.locale),
|
|
127
|
+
wholeCurrencyGrouping: false
|
|
128
|
+
} : ENTRY_SEPARATORS[mode];
|
|
116
129
|
const read = require_number_text.readNumberText(text, {
|
|
117
130
|
...separators,
|
|
118
131
|
currency: info.code
|
|
@@ -122,6 +135,11 @@ function parseAmountInput(text, currency, options = {}) {
|
|
|
122
135
|
reason: read.reason,
|
|
123
136
|
text: shown
|
|
124
137
|
};
|
|
138
|
+
if (mode === "locale" && readsAsGroupOrFraction(read, info)) return {
|
|
139
|
+
ok: false,
|
|
140
|
+
reason: "too_many_decimals",
|
|
141
|
+
text: shown
|
|
142
|
+
};
|
|
125
143
|
value = decimalOfText(read);
|
|
126
144
|
}
|
|
127
145
|
const result = entryMoney(value, info, {
|
|
@@ -196,7 +214,6 @@ function inputStep(currency) {
|
|
|
196
214
|
function inputPlaceholder(currency) {
|
|
197
215
|
return PLACEHOLDERS[require_money.resolveCurrency(currency, { allowHistoric: true }).storageDigits];
|
|
198
216
|
}
|
|
199
|
-
//#endregion
|
|
200
217
|
Object.defineProperty(exports, "decimalOfText", {
|
|
201
218
|
enumerable: true,
|
|
202
219
|
get: function() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { E as resolveCurrency, M as resolveLocale, N as separatorsOf, Q as MoneyError, at as fractionDigitsOf, c as toMajorDecimal, it as formatDecimal, n as fromMajor, q as trimDecimalZeros, s as storedDecimal, tt as compareDecimal } from "./money-
|
|
2
|
-
import { r as compare } from "./arithmetic-
|
|
3
|
-
import { n as readNumberText } from "./number-text-
|
|
4
|
-
|
|
1
|
+
import { E as resolveCurrency, M as resolveLocale, N as separatorsOf, Q as MoneyError, at as fractionDigitsOf, c as toMajorDecimal, it as formatDecimal, n as fromMajor, q as trimDecimalZeros, s as storedDecimal, tt as compareDecimal } from "./money-BxbAw7BP.js";
|
|
2
|
+
import { r as compare } from "./arithmetic-C97-YafD.js";
|
|
3
|
+
import { n as readNumberText } from "./number-text-Bj-gZXiF.js";
|
|
4
|
+
// src/input/amount.ts
|
|
5
5
|
/** Internal: the exact signed decimal a successful separator-engine read holds. */
|
|
6
6
|
function decimalOfText(read) {
|
|
7
7
|
return {
|
|
@@ -69,6 +69,15 @@ function echoesOriginal(shown, options) {
|
|
|
69
69
|
grouped: true
|
|
70
70
|
});
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* In a currency storing 0 digits, the locale's decimal separator before exactly three digits
|
|
74
|
+
* after a whole part of at most three (en-US `'1.234'`, `'19.999'`, `'1.000'` JPY) reads as a
|
|
75
|
+
* thousands group to some people and as a fraction to others; trailing zeros would even let
|
|
76
|
+
* `'1.000'` through as 1 yen. Neither reading is safe, so a locale field refuses it.
|
|
77
|
+
*/
|
|
78
|
+
function readsAsGroupOrFraction(read, info) {
|
|
79
|
+
return info.storageDigits === 0 && read.decimalMark !== null && read.scale === 3 && read.units < 1000000n;
|
|
80
|
+
}
|
|
72
81
|
/** Which reading a field's options ask for; `decimalSeparator` and `locale` together, or a `decimalSeparator` other than `'.'`, throw. */
|
|
73
82
|
function entryMode(options) {
|
|
74
83
|
if (options.decimalSeparator === void 0) return options.locale === void 0 ? "auto" : "locale";
|
|
@@ -92,17 +101,18 @@ const ENTRY_SEPARATORS = {
|
|
|
92
101
|
* against `,` (see `parseImportAmount`): `'29,99'` EUR is 29.99, `'1.299,00'` and `'1,299'`
|
|
93
102
|
* are 1299, KWD `'1,5'` is 1.5; a currency storing 0 digits rounds a fraction half-up to a
|
|
94
103
|
* whole unit (`'1200.5'` JPY is 1201).
|
|
95
|
-
* - With `locale`: its decimal separator is the decimal and its group separator is valid
|
|
96
|
-
* 3-digit (or Indian lakh) groups
|
|
97
|
-
* before exactly three digits
|
|
98
|
-
*
|
|
99
|
-
*
|
|
104
|
+
* - With `locale`: its decimal separator is always the decimal and its group separator is valid
|
|
105
|
+
* only in 3-digit (or Indian lakh) groups. In a currency that stores no fraction (JPY, VND) the
|
|
106
|
+
* decimal separator before exactly three digits is ambiguous (en-US `'1.234'` JPY could be
|
|
107
|
+
* 1,234 or 1.234) and is `'too_many_decimals'`, never read as grouping; grouping takes the
|
|
108
|
+
* locale's group separator (`'1,234'` en-US, `'1.234'` de-DE). en-US `'29,99'` USD is
|
|
109
|
+
* `'unparseable'`; de-DE `'29,99'` EUR is 29.99; en-US `'1200.5'` JPY is `'too_many_decimals'`.
|
|
100
110
|
* - With `decimalSeparator: '.'` (every `<input type="number">`): `.` is the decimal, nothing
|
|
101
111
|
* groups (`'1,299'` is `'unparseable'`), TND and IQD `'1.500'` are 1.5, and a currency storing
|
|
102
112
|
* 0 digits rounds a fraction half-up to a whole unit (`'1.5'` JPY is 2).
|
|
103
113
|
* Otherwise more fraction digits than the currency stores is `'too_many_decimals'`, never rounded
|
|
104
114
|
* (`'1,2345'` USD). Text equal to `original` returns it unchanged (JPY 120050 shown as
|
|
105
|
-
* `'1200.50'`). An unknown currency, or an `original` in another currency, throws.
|
|
115
|
+
* `'1200.50'`), except the ambiguous zero-digit text above. An unknown currency, or an `original` in another currency, throws.
|
|
106
116
|
*/
|
|
107
117
|
function parseAmountInput(text, currency, options = {}) {
|
|
108
118
|
const info = resolveCurrency(currency, { allowHistoric: true });
|
|
@@ -112,7 +122,10 @@ function parseAmountInput(text, currency, options = {}) {
|
|
|
112
122
|
let value;
|
|
113
123
|
if (echoesOriginal(shown, options)) value = storedDecimal(options.original).value;
|
|
114
124
|
else {
|
|
115
|
-
const separators = mode === "locale" ?
|
|
125
|
+
const separators = mode === "locale" ? {
|
|
126
|
+
...localeSeparators(options.locale),
|
|
127
|
+
wholeCurrencyGrouping: false
|
|
128
|
+
} : ENTRY_SEPARATORS[mode];
|
|
116
129
|
const read = readNumberText(text, {
|
|
117
130
|
...separators,
|
|
118
131
|
currency: info.code
|
|
@@ -122,6 +135,11 @@ function parseAmountInput(text, currency, options = {}) {
|
|
|
122
135
|
reason: read.reason,
|
|
123
136
|
text: shown
|
|
124
137
|
};
|
|
138
|
+
if (mode === "locale" && readsAsGroupOrFraction(read, info)) return {
|
|
139
|
+
ok: false,
|
|
140
|
+
reason: "too_many_decimals",
|
|
141
|
+
text: shown
|
|
142
|
+
};
|
|
125
143
|
value = decimalOfText(read);
|
|
126
144
|
}
|
|
127
145
|
const result = entryMoney(value, info, {
|
|
@@ -196,5 +214,4 @@ function inputStep(currency) {
|
|
|
196
214
|
function inputPlaceholder(currency) {
|
|
197
215
|
return PLACEHOLDERS[resolveCurrency(currency, { allowHistoric: true }).storageDigits];
|
|
198
216
|
}
|
|
199
|
-
//#endregion
|
|
200
217
|
export { inputStep as a, inputPlaceholder as i, formatAmountInput as n, localeSeparators as o, groupDigits as r, parseAmountInput as s, decimalOfText as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { $ as describeValue, B as decimalOf, E as resolveCurrency, Q as MoneyError, ot as isPositiveDecimal, s as storedDecimal, t as boundedMoney, ut as pow10 } from "./money-
|
|
2
|
-
import { t as mulDivToUnit } from "./rounding-
|
|
3
|
-
import { n as hundredPercent } from "./percent-
|
|
4
|
-
|
|
1
|
+
import { $ as describeValue, B as decimalOf, E as resolveCurrency, Q as MoneyError, ot as isPositiveDecimal, s as storedDecimal, t as boundedMoney, ut as pow10 } from "./money-BxbAw7BP.js";
|
|
2
|
+
import { t as mulDivToUnit } from "./rounding-CA1PVP3b.js";
|
|
3
|
+
import { n as hundredPercent } from "./percent-CB3Ixjc_.js";
|
|
4
|
+
// src/arithmetic.ts
|
|
5
5
|
/** Internal: the shared currency of `a` and `b` and their stored integers. Throws `currency_mismatch`. */
|
|
6
6
|
function pair(a, b, verb) {
|
|
7
7
|
const left = storedDecimal(a).info;
|
|
@@ -128,5 +128,4 @@ function excessOver(a, b, tolerance) {
|
|
|
128
128
|
const delta = x - y;
|
|
129
129
|
return boundedMoney(delta < limit ? 0n : delta, info);
|
|
130
130
|
}
|
|
131
|
-
//#endregion
|
|
132
131
|
export { within as _, equals as a, isPositive as c, min as d, multiply as f, sum as g, subtract as h, divide as i, isZero as l, remaining as m, clamp as n, excessOver as o, percentOf as p, compare as r, isNegative as s, add as t, max as u };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
const require_money = require("./money-
|
|
2
|
-
const require_rounding = require("./rounding-
|
|
3
|
-
const require_percent = require("./percent-
|
|
4
|
-
|
|
1
|
+
const require_money = require("./money-DWBjuuEK.cjs");
|
|
2
|
+
const require_rounding = require("./rounding-4QKTRbMQ.cjs");
|
|
3
|
+
const require_percent = require("./percent-DABHiSBs.cjs");
|
|
4
|
+
// src/arithmetic.ts
|
|
5
5
|
/** Internal: the shared currency of `a` and `b` and their stored integers. Throws `currency_mismatch`. */
|
|
6
6
|
function pair(a, b, verb) {
|
|
7
7
|
const left = require_money.storedDecimal(a).info;
|
|
@@ -128,7 +128,6 @@ function excessOver(a, b, tolerance) {
|
|
|
128
128
|
const delta = x - y;
|
|
129
129
|
return require_money.boundedMoney(delta < limit ? 0n : delta, info);
|
|
130
130
|
}
|
|
131
|
-
//#endregion
|
|
132
131
|
Object.defineProperty(exports, "add", {
|
|
133
132
|
enumerable: true,
|
|
134
133
|
get: function() {
|
package/dist/billing.cjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_money = require("./money-
|
|
3
|
-
const require_percent = require("./percent-
|
|
4
|
-
|
|
2
|
+
const require_money = require("./money-DWBjuuEK.cjs");
|
|
3
|
+
const require_percent = require("./percent-DABHiSBs.cjs");
|
|
4
|
+
// src/billing/credits.ts
|
|
5
5
|
function wholeNumber(value, name, min) {
|
|
6
6
|
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < min) throw new require_money.MoneyError("amount_invalid", `${name} must be an integer >= ${min}, got ${require_money.describeValue(value)}`);
|
|
7
7
|
return BigInt(value);
|
|
@@ -64,8 +64,7 @@ function bucketCredits(credits, step = 50) {
|
|
|
64
64
|
const s = wholeNumber(step, "step", 1);
|
|
65
65
|
return Number(require_money.divRound(c, s, "ceil") * s);
|
|
66
66
|
}
|
|
67
|
-
|
|
68
|
-
//#region src/billing/plans.ts
|
|
67
|
+
// src/billing/plans.ts
|
|
69
68
|
function checkSame(m, other, verb) {
|
|
70
69
|
const info = require_money.storedDecimal(m).info;
|
|
71
70
|
const own = require_money.storedDecimal(other).info;
|
|
@@ -144,8 +143,7 @@ function grossUpForPercentOffs(m, percents) {
|
|
|
144
143
|
if (!(kept > 0)) return m;
|
|
145
144
|
return require_money.boundedMoney(BigInt(Math.round(m.amount / kept)), info);
|
|
146
145
|
}
|
|
147
|
-
|
|
148
|
-
//#region src/billing/usage.ts
|
|
146
|
+
// src/billing/usage.ts
|
|
149
147
|
const USD_DIGITS = 6;
|
|
150
148
|
/**
|
|
151
149
|
* The exact sum of `quantity x rate x multiplier` over `lines`, rounded half-up once to 6
|
|
@@ -172,7 +170,6 @@ function usdCost(lines) {
|
|
|
172
170
|
}
|
|
173
171
|
return require_money.toDecimalText(require_money.roundToScale(total, USD_DIGITS, "half-up"));
|
|
174
172
|
}
|
|
175
|
-
//#endregion
|
|
176
173
|
exports.annualPrice = annualPrice;
|
|
177
174
|
exports.applyCoupons = applyCoupons;
|
|
178
175
|
exports.bucketCredits = bucketCredits;
|
package/dist/billing.d.cts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { R as DecimalText, i as Money } from "./money-
|
|
2
|
-
//#region src/billing/usage.d.ts
|
|
1
|
+
import { R as DecimalText, i as Money } from "./money-BkMeYDEW.cjs";
|
|
3
2
|
/** USD with sub-cent precision, as canonical decimal text (`'0.001234'`). Never merchant money. */
|
|
4
3
|
type UsdAmount = DecimalText;
|
|
5
4
|
interface UsdCostLine {
|
|
@@ -18,8 +17,6 @@ interface UsdCostLine {
|
|
|
18
17
|
* the cloner's `estimateCost`). Each line needs exactly one of `usdPerMillion` / `usdPerUnit`.
|
|
19
18
|
*/
|
|
20
19
|
export declare function usdCost(lines: readonly UsdCostLine[]): UsdAmount;
|
|
21
|
-
//#endregion
|
|
22
|
-
//#region src/billing/credits.d.ts
|
|
23
20
|
/**
|
|
24
21
|
* Credits for a USD cost: `ceil(usd x 100)` (0.07 gives 7, 1.1 gives 110, 0.0049 and 1e-7
|
|
25
22
|
* give 1). Text and Money are exact. A number is first rounded half-even to 8 decimals, so
|
|
@@ -37,8 +34,6 @@ export declare function creditsToUsd(credits: number): UsdAmount;
|
|
|
37
34
|
export declare function prorateCredits(quoted: number, share: number | DecimalText): number;
|
|
38
35
|
/** Credits rounded up to a whole `step` (default 50) for a quote: `ceil(credits / step) x step`. */
|
|
39
36
|
export declare function bucketCredits(credits: number, step?: number): number;
|
|
40
|
-
//#endregion
|
|
41
|
-
//#region src/billing/plans.d.ts
|
|
42
37
|
/** The yearly price: `yearly` when set, else `monthly x 12`. */
|
|
43
38
|
export declare function annualPrice(input: {
|
|
44
39
|
monthly: Money;
|
|
@@ -73,9 +68,6 @@ export declare function applyCoupons(m: Money, coupons: readonly Coupon[], optio
|
|
|
73
68
|
* the coupons take to 10).
|
|
74
69
|
*/
|
|
75
70
|
export declare function grossUpForPercentOffs(m: Money, percents: readonly (number | DecimalText)[]): Money;
|
|
76
|
-
//#endregion
|
|
77
|
-
//#region src/billing.d.ts
|
|
78
71
|
/** USD cents: Money in USD (USD stored amounts are cents). */
|
|
79
72
|
export type UsdCents = Money;
|
|
80
|
-
//#endregion
|
|
81
73
|
export type { Coupon, UsdAmount, UsdCostLine };
|