@amboras-dev/money 0.1.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 +25 -10
- 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-D7tm_t_o.cjs → allocate-AusholrC.cjs} +3 -4
- package/dist/{allocate-CdnhvAda.js → allocate-BBRfdaSC.js} +3 -4
- package/dist/{amount-DHtvZSSm.cjs → amount-CwFzcxUl.cjs} +29 -12
- package/dist/{amount-D7dj76nH.js → amount-DHFOK2Yd.js} +29 -12
- package/dist/{arithmetic-C-Q_aI49.js → arithmetic-C97-YafD.js} +4 -5
- package/dist/{arithmetic-Bq-8mSal.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-BnA1aLJk.cjs → format-92fzvTXY.cjs} +21 -6
- package/dist/{format-BdB7LEIp.js → format-BJC513u4.js} +16 -7
- 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 +13 -12
- package/dist/index.d.cts +28 -22
- package/dist/index.d.ts +28 -22
- package/dist/index.js +11 -13
- 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--gQWI1f2.cjs → iso-minor-DyNjT9A1.cjs} +2 -3
- package/dist/{iso-minor-CtaiIifs.js → iso-minor-odkY-e6I.js} +2 -3
- package/dist/{medusa-OJ2TJOxt.cjs → medusa-CzVK0bth.cjs} +24 -4
- package/dist/{medusa-DuQ3-KoD.d.cts → medusa-DofdvIgQ.d.cts} +11 -4
- package/dist/{medusa-RwqxaDGq.d.ts → medusa-DrbYmD2Y.d.ts} +11 -4
- package/dist/{medusa-CmxCni-G.js → medusa-pKGlImn9.js} +19 -5
- package/dist/{money-CbhsT0fB.d.cts → money-BkMeYDEW.d.cts} +19 -9
- package/dist/{money-CbhsT0fB.d.ts → money-BkMeYDEW.d.ts} +19 -9
- package/dist/{money-CIgELekG.js → money-BxbAw7BP.js} +25 -21
- package/dist/{money-D5-vlv0h.cjs → money-DWBjuuEK.cjs} +42 -20
- package/dist/{number-text-C82oh6JB.js → number-text-Bj-gZXiF.js} +3 -4
- package/dist/{number-text-BpvN6NAo.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-CKVI_c-m.js → percent-CB3Ixjc_.js} +2 -3
- package/dist/{percent-Dt8vNIAW.cjs → percent-DABHiSBs.cjs} +2 -3
- package/dist/{rate-CmeTGNex.d.ts → rate-BvvZUdSu.d.ts} +1 -3
- package/dist/{rate-toyS3EMf.d.cts → rate-CEakW3ia.d.cts} +1 -3
- package/dist/{rate-DGPfB_jc.cjs → rate-CEjWTATc.cjs} +3 -4
- package/dist/{rate-lLPViBuM.js → rate-D6S2_wgZ.js} +3 -4
- package/dist/{reporting-5xby-Zas.d.ts → reporting-DFNyDBdi.d.cts} +2 -4
- package/dist/{reporting-DEM97DhQ.d.cts → reporting-jIBILvnr.d.ts} +2 -4
- package/dist/{retail-BwcFJcVN.d.ts → retail-B04CjW3b.d.ts} +1 -3
- package/dist/{retail-DTk2XAMM.cjs → retail-CYGPU4ea.cjs} +4 -5
- package/dist/{retail-Mqd-sPMF.d.cts → retail-CmEyJQOH.d.cts} +1 -3
- package/dist/{retail-BsvpNv1F.js → retail-DXgs11ov.js} +4 -5
- package/dist/{rounding-oZ832yTD.cjs → rounding-4QKTRbMQ.cjs} +2 -3
- package/dist/{rounding-CVAPEfq_.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 +72675 -12
- package/dist/testing.d.cts +46 -11
- package/dist/testing.d.ts +46 -11
- package/dist/testing.js +72674 -13
- package/dist/{wire-CI3Sm1iC.js → wire-B4QU8O3W.js} +584 -114
- package/dist/wire-CVNicirK.d.ts +428 -0
- package/dist/wire-CtNJmWVP.d.cts +428 -0
- package/dist/{wire-DQZ6fA3c.cjs → wire-u2tmHY0Y.cjs} +649 -113
- package/dist/wire.cjs +12 -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/dist/wire-Cd4Q1Jmm.d.ts +0 -269
- package/dist/wire-hWuixv3w.d.cts +0 -269
package/README.md
CHANGED
|
@@ -121,8 +121,10 @@ corrupt one), `sameCurrency`, `medusaCurrencyCode` (the only sanctioned lower-ca
|
|
|
121
121
|
pinned to `@medusajs/utils` 2.13.4). `CurrencyInfo` also carries `symbolAliases`, `localeHint` and `commaDecimal` (TND: its decimal mark is a comma, so `'12,500'` is 12.5).
|
|
122
122
|
|
|
123
123
|
**Medusa shapes**: `readStored` reads `.value ?? .raw.value ?? .raw_.value`, then
|
|
124
|
-
`.numeric ?? .numeric_`, then the primitive
|
|
124
|
+
`.numeric ?? .numeric_`, then the primitive (`precision: 'round'` rounds a fractional amount
|
|
125
|
+
half-up to a whole storage unit instead of refusing it). `readStoredField` (`raw_<key>` first, no fallback
|
|
125
126
|
when it is unreadable), `readStoredDecimal` (keeps fractions, canonical text),
|
|
127
|
+
`readStoredLiteral` (the amount literal verbatim, for SQL and ledger columns),
|
|
126
128
|
`readCalculatedPrice`, `toMedusaRaw` (never an exponent), `toMedusaAmountInput` (`'0'` for zero),
|
|
127
129
|
`moneyByCurrency` / `moneyFor` (per-currency settings and price lists), `isPositiveAmount`.
|
|
128
130
|
|
|
@@ -144,12 +146,15 @@ unit apart).
|
|
|
144
146
|
|
|
145
147
|
**Decimal text**: `parseDecimalText`, `isDecimalLiteral` (the one storage-literal grammar),
|
|
146
148
|
`assertDecimalLiteral`, `trimDecimalZeros`, `decimalSum`, `decimalSubtract`, `decimalMultiply`,
|
|
147
|
-
`decimalDivide`, `decimalRound`, `decimalCompare`, `decimalClamp`, `decimalWithin
|
|
148
|
-
and exponents are refused.
|
|
149
|
+
`decimalDivide`, `decimalRound`, `decimalCompare`, `decimalClamp`, `decimalWithin`, `decimalEquals`
|
|
150
|
+
(never throws: a non-decimal is never equal). Exact; `''` and exponents are refused.
|
|
149
151
|
|
|
150
152
|
**Formatting** (for people, not byte-stable across ICU): `formatMoney` (display digits, or
|
|
151
153
|
`widen` / `trim-zero`; explicit locale, default `en-US`), `formatMoneyCompact`,
|
|
152
|
-
`formatMoneyRange`, `moneyAxisFormatter
|
|
154
|
+
`formatMoneyRange`, `moneyAxisFormatter`, `formatStored` (a possibly fractional stored amount,
|
|
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).
|
|
153
158
|
|
|
154
159
|
Internals other entry points build on (never exported), each the only implementation of its concept:
|
|
155
160
|
`internal/number-text.ts` `readNumberText` (the one separator engine), `internal/rational.ts`
|
|
@@ -165,14 +170,20 @@ import { money } from '@amboras-dev/money'
|
|
|
165
170
|
import { ISO_MINOR_V2, conventionOf, decodeWire, encodeWire } from '@amboras-dev/money/wire'
|
|
166
171
|
|
|
167
172
|
encodeWire('stripe', money(120000, 'JPY'), { convention: ISO_MINOR_V2 }) // 1200
|
|
168
|
-
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
|
|
175
|
+
encodeWire('mollie', { amount: '107.8175', currency: 'USD' }) // '1.08': a fractional stored total
|
|
169
176
|
```
|
|
170
177
|
|
|
171
|
-
|
|
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.
|
|
179
|
+
|
|
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`.
|
|
172
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).
|
|
173
|
-
-
|
|
174
|
-
-
|
|
175
|
-
-
|
|
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`.
|
|
176
187
|
- Provider text: `parseProviderAmount` (carrier quotes, dispute amounts; `decimalSeparator: 'auto'` is the `/input` rule).
|
|
177
188
|
- Destinations: `destinationAmount` (pixels, ad platforms, email tools), `feedPrice`, `googleMicros`, `eventMoney` (ClickHouse `Int64` contract).
|
|
178
189
|
- Plugin slots: `slotMoney`, `readSlotMoney`.
|
|
@@ -253,6 +264,7 @@ inputStep('KWD') // '0.001'
|
|
|
253
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.
|
|
254
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.
|
|
255
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).
|
|
256
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).
|
|
257
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).
|
|
258
270
|
It equals that function on every `.`-only value within the storage digits (`test/input/input.test.ts`), except one class:
|
|
@@ -263,7 +275,7 @@ inputStep('KWD') // '0.001'
|
|
|
263
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).
|
|
264
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.
|
|
265
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`).
|
|
266
|
-
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).
|
|
267
279
|
- `inputStep`, `inputPlaceholder`: from the storage digits, never a hardcoded `0.01`.
|
|
268
280
|
- `parseImportAmount(text, { decimalSeparator: 'auto' | '.' | ',', currency, allowNegative, allowTrailingText })`: CSV, supplier, scraped and carrier text to `DecimalText` (major units), or `null`.
|
|
269
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:
|
|
@@ -310,6 +322,8 @@ Framework-agnostic: no test runner or fast-check dependency.
|
|
|
310
322
|
`/wire`'s `encodeWire` must reproduce every vector (decision D2: wire bytes feed idempotency keys, Redis keys and HMAC strings).
|
|
311
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> ...`).
|
|
312
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.
|
|
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.
|
|
313
327
|
- `DESTINATION_GOLDEN_VECTORS`: what `destinationAmount`, `feedPrice` and `googleMicros` must give per destination.
|
|
314
328
|
- `checkWireDigits(psp, documented)`: the capability-consistency probe; returns mismatch messages.
|
|
315
329
|
- `normalizeIntlSpaces(s)`, `sampleMoney(currency, major)`.
|
|
@@ -331,6 +345,7 @@ The full list, with the decision behind each, is in [`docs/money/API-SPEC.md`](.
|
|
|
331
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.
|
|
332
346
|
This is deliberate: it is what the PSPs charge and what people read.
|
|
333
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.
|
|
334
349
|
- Catalog feeds and destinations drop the sub-unit the same way: `feedPrice(money(435050, 'HUF'))` is `'4351 HUF'`, IQD 1500 is `'2 IQD'`.
|
|
335
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'`.
|
|
336
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 {
|
|
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 {
|
|
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 {
|
|
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 {
|
|
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,
|
|
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 {
|
|
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;
|