@classytic/primitives 0.18.0 → 0.20.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/CHANGELOG.md +906 -715
- package/README.md +160 -159
- package/dist/approval.d.mts +49 -10
- package/dist/approval.mjs +44 -10
- package/dist/bank-transaction.d.mts +1 -1
- package/dist/cadence-BFQVC1Qp.d.mts +107 -0
- package/dist/cadence.d.mts +1 -92
- package/dist/cadence.mjs +133 -4
- package/dist/calendar-B6fm-FbR.d.mts +46 -0
- package/dist/calendar.d.mts +1 -45
- package/dist/canonical.mjs +17 -0
- package/dist/context.d.mts +28 -1
- package/dist/currency.d.mts +2 -2
- package/dist/currency.mjs +134 -19
- package/dist/event-infra.d.mts +1 -34
- package/dist/event-infra.mjs +16 -46
- package/dist/idempotency.d.mts +253 -0
- package/dist/idempotency.mjs +226 -0
- package/dist/memory-outbox.d.mts +64 -0
- package/dist/memory-outbox.mjs +172 -0
- package/dist/monetization.d.mts +195 -0
- package/dist/monetization.mjs +79 -0
- package/dist/money-DPo9fLBi.d.mts +347 -0
- package/dist/money.d.mts +1 -140
- package/dist/money.mjs +6 -15
- package/dist/outbox.d.mts +49 -1
- package/dist/outbox.mjs +64 -1
- package/dist/payment-events.d.mts +132 -4
- package/dist/payment-events.mjs +22 -2
- package/dist/payment-gateway.d.mts +210 -7
- package/dist/payment-gateway.mjs +18 -1
- package/dist/period.d.mts +138 -7
- package/dist/period.mjs +316 -5
- package/dist/person-parse.d.mts +82 -0
- package/dist/person-parse.mjs +109 -0
- package/dist/proration.d.mts +76 -0
- package/dist/proration.mjs +132 -0
- package/dist/sla-policy.d.mts +1 -1
- package/dist/state-machine.mjs +13 -0
- package/dist/suspension.d.mts +1 -1
- package/dist/timezone-tlG7ygwx.d.mts +114 -0
- package/dist/timezone.d.mts +2 -100
- package/dist/timezone.mjs +38 -3
- package/dist/unit-cost-rate.d.mts +1 -2
- package/package.json +244 -224
- package/dist/currency-BCJnJBlb.d.mts +0 -119
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
import { t as Brand } from "./brand-LLXqoh5J.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/money/currency.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* ISO 4217 currency code (3 uppercase letters).
|
|
6
|
+
*
|
|
7
|
+
* Branded `string` — any 3-letter string is structurally valid, but the brand
|
|
8
|
+
* prevents accidental mixing with raw strings. Use {@link toCurrencyCode} to
|
|
9
|
+
* construct one safely.
|
|
10
|
+
*/
|
|
11
|
+
type CurrencyCode = Brand<string, 'CurrencyCode'>;
|
|
12
|
+
/**
|
|
13
|
+
* Common ISO 4217 codes. Non-exhaustive — add more at the consumer level if
|
|
14
|
+
* needed. Listed here because shared-types ships with these by default.
|
|
15
|
+
*/
|
|
16
|
+
declare const CURRENCIES: {
|
|
17
|
+
readonly USD: "USD";
|
|
18
|
+
readonly EUR: "EUR";
|
|
19
|
+
readonly GBP: "GBP";
|
|
20
|
+
readonly JPY: "JPY";
|
|
21
|
+
readonly CNY: "CNY";
|
|
22
|
+
readonly INR: "INR";
|
|
23
|
+
readonly BDT: "BDT";
|
|
24
|
+
readonly AED: "AED";
|
|
25
|
+
readonly SAR: "SAR";
|
|
26
|
+
readonly PKR: "PKR";
|
|
27
|
+
readonly NPR: "NPR";
|
|
28
|
+
readonly LKR: "LKR";
|
|
29
|
+
readonly MYR: "MYR";
|
|
30
|
+
readonly SGD: "SGD";
|
|
31
|
+
readonly AUD: "AUD";
|
|
32
|
+
readonly CAD: "CAD";
|
|
33
|
+
readonly CHF: "CHF";
|
|
34
|
+
readonly HKD: "HKD";
|
|
35
|
+
readonly THB: "THB";
|
|
36
|
+
readonly IDR: "IDR";
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Number of minor units per major unit, by ISO 4217 code.
|
|
40
|
+
*
|
|
41
|
+
* **This table must list EVERY currency whose exponent is not 2.** ISO 4217
|
|
42
|
+
* defaults to 2 decimals, and {@link minorUnitFactor} falls back to 100 for a
|
|
43
|
+
* code it does not know — which is correct for the ~150 two-decimal currencies
|
|
44
|
+
* and catastrophically wrong for the others. A missing zero-decimal entry does
|
|
45
|
+
* not throw: it makes `fromMajor(1500, 'GNF')` store 150 000 minor units, a
|
|
46
|
+
* 100× error that reads as a plausible number all the way to the gateway.
|
|
47
|
+
*
|
|
48
|
+
* So the exceptions are enumerated EXHAUSTIVELY (all 0-, 3- and 4-decimal
|
|
49
|
+
* codes), leaving the `?? 100` fallback reachable only by a code that is not a
|
|
50
|
+
* real ISO 4217 currency — a typo, a lowercased code, or a placeholder. The
|
|
51
|
+
* two-decimal entries below are redundant with the default and kept only as
|
|
52
|
+
* documentation of the common set.
|
|
53
|
+
*/
|
|
54
|
+
declare const MINOR_UNIT_FACTOR: Readonly<Record<string, number>>;
|
|
55
|
+
/**
|
|
56
|
+
* ISO 4217 format — 3 uppercase letters. Exported so consumers can embed
|
|
57
|
+
* the SAME pattern in JSON-Schema-representable validators (zod `.regex`,
|
|
58
|
+
* Mongoose `match`, OpenAPI `pattern`) instead of hand-rolling a copy:
|
|
59
|
+
* `.refine(isCurrencyCode)` validates at runtime but is dropped by
|
|
60
|
+
* `z.toJSONSchema`, losing the constraint from generated API docs.
|
|
61
|
+
* `isCurrencyCode` / `toCurrencyCode` remain the runtime API.
|
|
62
|
+
*/
|
|
63
|
+
declare const CURRENCY_PATTERN: RegExp;
|
|
64
|
+
/**
|
|
65
|
+
* Narrow a raw string to a {@link CurrencyCode}. Returns `null` if the string
|
|
66
|
+
* is not a valid ISO 4217 format (3 uppercase letters). This does not verify
|
|
67
|
+
* the code is registered with ISO — use your own whitelist for that.
|
|
68
|
+
*/
|
|
69
|
+
declare function toCurrencyCode(value: string): CurrencyCode | null;
|
|
70
|
+
/**
|
|
71
|
+
* Validate + brand, or throw. The constructor form — same relationship to
|
|
72
|
+
* {@link toCurrencyCode} that `civilDate` has to `isCivilDate`.
|
|
73
|
+
*
|
|
74
|
+
* `Money` carries a {@link CurrencyCode}, not a bare string, and this is where
|
|
75
|
+
* an untrusted value earns the brand. Rejecting `'jpy'` here matters: lowercase
|
|
76
|
+
* misses {@link MINOR_UNIT_FACTOR}, so it silently becomes a 2-decimal currency
|
|
77
|
+
* and every JPY amount is off by 100×.
|
|
78
|
+
*/
|
|
79
|
+
declare function currencyCode(value: string): CurrencyCode;
|
|
80
|
+
declare class InvalidCurrencyCodeError extends Error {
|
|
81
|
+
readonly name = "InvalidCurrencyCodeError";
|
|
82
|
+
readonly value: string;
|
|
83
|
+
constructor(value: string);
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Raised when an operation combines two currencies.
|
|
87
|
+
*
|
|
88
|
+
* Declared HERE rather than in `/money` so the FX seam below can throw it
|
|
89
|
+
* without a runtime import back into `/money` (which imports
|
|
90
|
+
* {@link minorUnitFactor} from this file). `/money` re-exports it, so
|
|
91
|
+
* `import { CurrencyMismatchError } from '@classytic/primitives/money'` still
|
|
92
|
+
* resolves.
|
|
93
|
+
*/
|
|
94
|
+
declare class CurrencyMismatchError extends Error {
|
|
95
|
+
readonly name = "CurrencyMismatchError";
|
|
96
|
+
readonly left: string;
|
|
97
|
+
readonly right: string;
|
|
98
|
+
constructor(left: string, right: string);
|
|
99
|
+
}
|
|
100
|
+
/** Type predicate for `unknown` input. */
|
|
101
|
+
declare function isCurrencyCode(value: unknown): value is CurrencyCode;
|
|
102
|
+
/**
|
|
103
|
+
* Minor units per major unit (100 for USD, 1 for JPY, 1000 for KWD, 10 000 for CLF).
|
|
104
|
+
*
|
|
105
|
+
* Falls back to 100 for an unlisted code — the ISO 4217 default exponent. Because
|
|
106
|
+
* {@link MINOR_UNIT_FACTOR} enumerates every non-2-decimal currency, that fallback
|
|
107
|
+
* is now only reached by codes that are not ISO 4217 currencies at all; use
|
|
108
|
+
* {@link isKnownCurrency} at a config/boot boundary if you need to reject those
|
|
109
|
+
* rather than assume two decimals for them.
|
|
110
|
+
*/
|
|
111
|
+
declare function minorUnitFactor(currency: string): number;
|
|
112
|
+
/**
|
|
113
|
+
* Does {@link MINOR_UNIT_FACTOR} carry an explicit exponent for this code?
|
|
114
|
+
*
|
|
115
|
+
* `false` means {@link minorUnitFactor} would ASSUME two decimals. That
|
|
116
|
+
* assumption is right for an ordinary ISO 4217 code and wrong by 100× for a
|
|
117
|
+
* mistyped or lowercased one (`'jpy'`), so a boot-time config validator that
|
|
118
|
+
* accepts an operator-supplied currency should gate on this instead of letting
|
|
119
|
+
* the default answer for it.
|
|
120
|
+
*/
|
|
121
|
+
declare function isKnownCurrency(currency: string): boolean;
|
|
122
|
+
/**
|
|
123
|
+
* Frozen FX conversion record — the cost of record for cross-currency
|
|
124
|
+
* transactions. Stored alongside a base-currency amount at the seam where
|
|
125
|
+
* currency crossed (procurement receipt, landed-cost entry, foreign refund).
|
|
126
|
+
*
|
|
127
|
+
* Invariant: once written, never mutated. Revaluation is a new snapshot on
|
|
128
|
+
* a new document — rewriting an existing snapshot corrupts audit history.
|
|
129
|
+
*
|
|
130
|
+
* `rate` is expressed as: 1 sourceCurrency = rate * baseCurrency
|
|
131
|
+
* (e.g. USD→BDT at 110 means rate = 110, so sourceAmount 10 USD → baseAmount 1100 BDT).
|
|
132
|
+
* This matches how banks quote — the source is the "from" currency.
|
|
133
|
+
*/
|
|
134
|
+
interface FxSnapshot {
|
|
135
|
+
/** Currency the transaction was invoiced / quoted in. ISO 4217. */
|
|
136
|
+
sourceCurrency: string;
|
|
137
|
+
/** Organisation's functional currency the base amount is expressed in. */
|
|
138
|
+
baseCurrency: string;
|
|
139
|
+
/** 1 unit of sourceCurrency equals this many units of baseCurrency. */
|
|
140
|
+
rate: number;
|
|
141
|
+
/** When the rate was captured — NOT when the document was last touched. */
|
|
142
|
+
snapshotAt: Date;
|
|
143
|
+
/**
|
|
144
|
+
* Optional provenance tag — e.g. 'manual', 'bangladesh-bank',
|
|
145
|
+
* 'openexchangerates.org', 'supplier-invoice'. Free-form; hosts use it for
|
|
146
|
+
* audit filtering and re-pricing policy.
|
|
147
|
+
*/
|
|
148
|
+
source?: string;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* How to land a converted amount on an integer number of minor units.
|
|
152
|
+
* Required to be deterministic and named, never implicit — the old signature
|
|
153
|
+
* returned an unrounded float and left the choice to whoever persisted it, so
|
|
154
|
+
* two call sites rounding differently produced two answers for one conversion.
|
|
155
|
+
*/
|
|
156
|
+
type FxRounding = 'half-away-from-zero' | 'floor' | 'ceil';
|
|
157
|
+
/**
|
|
158
|
+
* Apply an {@link FxSnapshot} to a source-currency amount.
|
|
159
|
+
*
|
|
160
|
+
* ## Why this takes `Money` and not a number
|
|
161
|
+
*
|
|
162
|
+
* It used to take a bare `number`, which meant nothing could check the amount
|
|
163
|
+
* was denominated in `fx.sourceCurrency`. Applying a USD→BDT snapshot to a EUR
|
|
164
|
+
* amount type-checked, ran, and returned a plausible wrong number — the
|
|
165
|
+
* arithmetic is identical, only the meaning is wrong, so no test that asserts a
|
|
166
|
+
* total would notice. Taking `Money` lets the seam enforce its own
|
|
167
|
+
* precondition: a mismatch throws {@link CurrencyMismatchError}.
|
|
168
|
+
*
|
|
169
|
+
* ## Minor-unit exponents are handled
|
|
170
|
+
*
|
|
171
|
+
* The rate is quoted in MAJOR units (1 source = `rate` base), while `Money`
|
|
172
|
+
* holds minor units, and the two currencies need not share an exponent
|
|
173
|
+
* (USD has 2, JPY has 0). The conversion therefore goes
|
|
174
|
+
* `minor → major → ×rate → base minor`, not a naive `amount × rate`, which is
|
|
175
|
+
* off by 100× for any pair whose exponents differ.
|
|
176
|
+
*/
|
|
177
|
+
declare function convertWithSnapshot(source: Money, fx: FxSnapshot, options?: {
|
|
178
|
+
rounding?: FxRounding;
|
|
179
|
+
}): Money;
|
|
180
|
+
/**
|
|
181
|
+
* Reverse an {@link FxSnapshot} — reconstruct the source-currency amount
|
|
182
|
+
* from a stored base amount. Useful for reporting "cost in original
|
|
183
|
+
* currency". Not round-trip safe under rounding; prefer storing the
|
|
184
|
+
* original source amount alongside the base amount when audit fidelity
|
|
185
|
+
* matters.
|
|
186
|
+
*
|
|
187
|
+
* Symmetrically guarded: a base amount that is not in `fx.baseCurrency` throws
|
|
188
|
+
* rather than dividing by a rate that does not describe it.
|
|
189
|
+
*/
|
|
190
|
+
declare function reverseWithSnapshot(base: Money, fx: FxSnapshot, options?: {
|
|
191
|
+
rounding?: FxRounding;
|
|
192
|
+
}): Money;
|
|
193
|
+
/**
|
|
194
|
+
* Structural validation — checks shape only. Does NOT verify the rate is
|
|
195
|
+
* economically sensible (8.5 USD→BDT would pass this check). Host-level
|
|
196
|
+
* guards / anomaly detection handle that.
|
|
197
|
+
*/
|
|
198
|
+
declare function isFxSnapshot(value: unknown): value is FxSnapshot;
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/money/money.d.ts
|
|
201
|
+
/**
|
|
202
|
+
* Wire-format monetary amount: integer minor units, branded with a phantom
|
|
203
|
+
* currency type so calls like `formatBdt(usdValue)` fail at compile time.
|
|
204
|
+
*
|
|
205
|
+
* Use this for HTTP/JSON fields where transmitting a `Money` struct would be
|
|
206
|
+
* overkill: `interface Order { grandTotal: MinorUnits<'BDT'>; ... }`. Pair
|
|
207
|
+
* with the structured {@link Money} for in-process arithmetic.
|
|
208
|
+
*
|
|
209
|
+
* Erases at runtime — branded types have zero cost.
|
|
210
|
+
*
|
|
211
|
+
* @example
|
|
212
|
+
* type Paisa = MinorUnits<'BDT'>;
|
|
213
|
+
* type UsdCents = MinorUnits<'USD'>;
|
|
214
|
+
* const p: Paisa = MinorUnits(489900, 'BDT');
|
|
215
|
+
* const c: UsdCents = p; // ❌ type error: BDT vs USD
|
|
216
|
+
*/
|
|
217
|
+
declare const __unitBrand: unique symbol;
|
|
218
|
+
declare const __currencyBrand: unique symbol;
|
|
219
|
+
type MinorUnits<C extends string = string> = number & {
|
|
220
|
+
readonly [__unitBrand]: 'minor';
|
|
221
|
+
readonly [__currencyBrand]: C;
|
|
222
|
+
};
|
|
223
|
+
/**
|
|
224
|
+
* Cast a raw number to {@link MinorUnits}. Pass the currency as the second
|
|
225
|
+
* argument to fix the phantom type. With no second arg, `MinorUnits<string>`
|
|
226
|
+
* — useful for generic SDK boundaries that don't know the currency yet.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* MinorUnits(489900, 'BDT') // typed MinorUnits<'BDT'>
|
|
230
|
+
* MinorUnits(199, 'USD') // typed MinorUnits<'USD'>
|
|
231
|
+
*/
|
|
232
|
+
declare function MinorUnits<C extends string = string>(amount: number, _currency?: C): MinorUnits<C>;
|
|
233
|
+
/**
|
|
234
|
+
* A monetary amount stored as an integer in the currency's minor unit.
|
|
235
|
+
*
|
|
236
|
+
* Examples:
|
|
237
|
+
* - `{ amount: 19_99, currency: 'USD' }` — 19.99 USD (2 decimals)
|
|
238
|
+
* - `{ amount: 1_500, currency: 'JPY' }` — 1500 JPY (0 decimals)
|
|
239
|
+
* - `{ amount: 500_000, currency: 'KWD' }` — 500.000 KWD (3 decimals)
|
|
240
|
+
*
|
|
241
|
+
* Storing as integers avoids IEEE 754 rounding across arithmetic. Never store
|
|
242
|
+
* `Money.amount` as a float.
|
|
243
|
+
*
|
|
244
|
+
* ## `currency` is a branded {@link CurrencyCode}, not a string
|
|
245
|
+
*
|
|
246
|
+
* It was declared `CurrencyCode | string`, and a union with `string` collapses
|
|
247
|
+
* TO `string` — so the brand was decoration on the one type in the package
|
|
248
|
+
* where it matters, and `money()` validated nothing. Any string reached the
|
|
249
|
+
* field: `'jpy'`, `''`, `'US Dollar'`, a mistyped `'BTD'`. None of those are in
|
|
250
|
+
* {@link MINOR_UNIT_FACTOR}, so `minorUnitFactor` assumes two decimals for them
|
|
251
|
+
* and every JPY-style amount is 100× wrong while looking perfectly ordinary.
|
|
252
|
+
*
|
|
253
|
+
* Construct through {@link money} / {@link fromMajor} (which validate and
|
|
254
|
+
* brand) or through `currencyCode()` at your own boundary. A raw object literal
|
|
255
|
+
* `{ amount, currency: someString }` is now a compile error — deliberately, so
|
|
256
|
+
* the validation cannot be bypassed by the shape that skipped it before.
|
|
257
|
+
*/
|
|
258
|
+
interface Money {
|
|
259
|
+
readonly amount: number;
|
|
260
|
+
readonly currency: CurrencyCode;
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Construct {@link Money} from an integer minor-unit amount.
|
|
264
|
+
*
|
|
265
|
+
* @throws TypeError if `amount` is not a finite integer.
|
|
266
|
+
* @throws InvalidCurrencyCodeError if `currency` is not ISO 4217 format.
|
|
267
|
+
*/
|
|
268
|
+
declare function money(amount: number, currency: string): Money;
|
|
269
|
+
/**
|
|
270
|
+
* Construct {@link Money} from a major-unit number (e.g. dollars, taka) by
|
|
271
|
+
* scaling to the currency's minor unit and rounding half-away-from-zero.
|
|
272
|
+
*
|
|
273
|
+
* Uses `toPrecision(15)` to strip the trailing-bit noise that appears in
|
|
274
|
+
* multiply-then-round (e.g. `0.1 + 0.2 = 0.30000000000000004`). This is
|
|
275
|
+
* enough for the vast majority of decimals encountered in real-world pricing.
|
|
276
|
+
*
|
|
277
|
+
* **Precision caveat.** A handful of decimals cannot be represented exactly
|
|
278
|
+
* in IEEE 754 and will round to the IEEE-closest integer rather than the
|
|
279
|
+
* mathematically-expected one — e.g. `1.005` is stored as `1.00499999…` and
|
|
280
|
+
* rounds to `100` minor units, not `101`. If you need decimal-exact rounding,
|
|
281
|
+
* construct money from a string or use integer minor units directly via
|
|
282
|
+
* {@link money}.
|
|
283
|
+
*
|
|
284
|
+
* @example
|
|
285
|
+
* fromMajor(19.99, 'USD') // { amount: 1999, currency: 'USD' }
|
|
286
|
+
* fromMajor(0.1 + 0.2, 'USD') // { amount: 30, currency: 'USD' }
|
|
287
|
+
* fromMajor(1500, 'JPY') // { amount: 1500, currency: 'JPY' }
|
|
288
|
+
*/
|
|
289
|
+
declare function fromMajor(major: number, currency: string): Money;
|
|
290
|
+
/** Convert {@link Money} back to its major-unit floating-point representation. */
|
|
291
|
+
declare function toMajor(m: Money): number;
|
|
292
|
+
declare function addMoney(a: Money, b: Money): Money;
|
|
293
|
+
declare function subtractMoney(a: Money, b: Money): Money;
|
|
294
|
+
/**
|
|
295
|
+
* Multiply money by a scalar. The result is rounded to an integer number of
|
|
296
|
+
* minor units using half-away-from-zero rounding.
|
|
297
|
+
*/
|
|
298
|
+
declare function multiplyMoney(m: Money, scalar: number): Money;
|
|
299
|
+
/** Sum a list of {@link Money}. Returns zero in the given currency for empty input. */
|
|
300
|
+
declare function sumMoney(values: readonly Money[], currency: string): Money;
|
|
301
|
+
/** Strict equality (amount and currency both match). */
|
|
302
|
+
declare function equalsMoney(a: Money, b: Money): boolean;
|
|
303
|
+
/**
|
|
304
|
+
* Ordering predicate. Returns -1, 0, or 1. Throws on currency mismatch — use
|
|
305
|
+
* {@link equalsMoney} for a total equality check across currencies.
|
|
306
|
+
*/
|
|
307
|
+
declare function compareMoney(a: Money, b: Money): -1 | 0 | 1;
|
|
308
|
+
declare function isZeroMoney(m: Money): boolean;
|
|
309
|
+
declare function isPositiveMoney(m: Money): boolean;
|
|
310
|
+
declare function isNegativeMoney(m: Money): boolean;
|
|
311
|
+
declare function negateMoney(m: Money): Money;
|
|
312
|
+
declare function absMoney(m: Money): Money;
|
|
313
|
+
/**
|
|
314
|
+
* Decimal major-unit number → integer minor units by decimal exponent.
|
|
315
|
+
* Rounding: raw IEEE `Math.round` — half-up toward +∞ (so `-10.5 → -10`),
|
|
316
|
+
* documented above. Throws when the result leaves the safe-integer range
|
|
317
|
+
* (also catches `NaN`/`Infinity` inputs).
|
|
318
|
+
*
|
|
319
|
+
* @example
|
|
320
|
+
* majorToMinorUnits(10.5) // 1050
|
|
321
|
+
* majorToMinorUnits(1000, 0) // 1000 (JPY-style)
|
|
322
|
+
* majorToMinorUnits(1.005) // 100 — IEEE, see note above
|
|
323
|
+
*/
|
|
324
|
+
declare function majorToMinorUnits(major: number, minorUnitDecimals?: number): number;
|
|
325
|
+
/** Integer minor units → decimal major-unit number by decimal exponent: `1050 → 10.5`. */
|
|
326
|
+
declare function minorUnitsToMajor(minor: number, minorUnitDecimals?: number): number;
|
|
327
|
+
/**
|
|
328
|
+
* Percentage of an integer minor-unit amount → integer minor units.
|
|
329
|
+
* EXACT multiply-then-round: `Math.round((minor × ratePercent) / 100)` —
|
|
330
|
+
* one rounding point, half-up toward +∞. Handles genuinely fractional rates
|
|
331
|
+
* (QST 9.975 %) without precision loss: `percentOfMinor(20000, 9.975) === 1995`.
|
|
332
|
+
*
|
|
333
|
+
* Hosts whose domain guarantees at-most-2-decimal rates MAY snap the rate to
|
|
334
|
+
* integer basis points BEFORE calling (be-prod's `#shared/money` seam does) —
|
|
335
|
+
* that is host policy layered on top, not part of this primitive.
|
|
336
|
+
*/
|
|
337
|
+
declare function percentOfMinor(minor: number, ratePercent: number): number;
|
|
338
|
+
/**
|
|
339
|
+
* Integer minor units → plain decimal string (no currency symbol, no locale):
|
|
340
|
+
* `formatMinorUnits(10550) → "105.50"`, `formatMinorUnits(1000, 0) → "1000"`.
|
|
341
|
+
* For localized currency strings use `Intl.NumberFormat` at the display layer.
|
|
342
|
+
*/
|
|
343
|
+
declare function formatMinorUnits(minor: number, minorUnitDecimals?: number): string;
|
|
344
|
+
/** Structural type guard. */
|
|
345
|
+
declare function isMoney(value: unknown): value is Money;
|
|
346
|
+
//#endregion
|
|
347
|
+
export { convertWithSnapshot as A, CURRENCY_PATTERN as C, FxSnapshot as D, FxRounding as E, minorUnitFactor as F, reverseWithSnapshot as I, toCurrencyCode as L, isCurrencyCode as M, isFxSnapshot as N, InvalidCurrencyCodeError as O, isKnownCurrency as P, CURRENCIES as S, CurrencyMismatchError as T, negateMoney as _, compareMoney as a, sumMoney as b, fromMajor as c, isPositiveMoney as d, isZeroMoney as f, multiplyMoney as g, money as h, addMoney as i, currencyCode as j, MINOR_UNIT_FACTOR as k, isMoney as l, minorUnitsToMajor as m, Money as n, equalsMoney as o, majorToMinorUnits as p, absMoney as r, formatMinorUnits as s, MinorUnits as t, isNegativeMoney as u, percentOfMinor as v, CurrencyCode as w, toMajor as x, subtractMoney as y };
|
package/dist/money.d.mts
CHANGED
|
@@ -1,141 +1,2 @@
|
|
|
1
|
-
import { r as
|
|
2
|
-
|
|
3
|
-
//#region src/money/money.d.ts
|
|
4
|
-
/**
|
|
5
|
-
* Wire-format monetary amount: integer minor units, branded with a phantom
|
|
6
|
-
* currency type so calls like `formatBdt(usdValue)` fail at compile time.
|
|
7
|
-
*
|
|
8
|
-
* Use this for HTTP/JSON fields where transmitting a `Money` struct would be
|
|
9
|
-
* overkill: `interface Order { grandTotal: MinorUnits<'BDT'>; ... }`. Pair
|
|
10
|
-
* with the structured {@link Money} for in-process arithmetic.
|
|
11
|
-
*
|
|
12
|
-
* Erases at runtime — branded types have zero cost.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* type Paisa = MinorUnits<'BDT'>;
|
|
16
|
-
* type UsdCents = MinorUnits<'USD'>;
|
|
17
|
-
* const p: Paisa = MinorUnits(489900, 'BDT');
|
|
18
|
-
* const c: UsdCents = p; // ❌ type error: BDT vs USD
|
|
19
|
-
*/
|
|
20
|
-
declare const __unitBrand: unique symbol;
|
|
21
|
-
declare const __currencyBrand: unique symbol;
|
|
22
|
-
type MinorUnits<C extends string = string> = number & {
|
|
23
|
-
readonly [__unitBrand]: 'minor';
|
|
24
|
-
readonly [__currencyBrand]: C;
|
|
25
|
-
};
|
|
26
|
-
/**
|
|
27
|
-
* Cast a raw number to {@link MinorUnits}. Pass the currency as the second
|
|
28
|
-
* argument to fix the phantom type. With no second arg, `MinorUnits<string>`
|
|
29
|
-
* — useful for generic SDK boundaries that don't know the currency yet.
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* MinorUnits(489900, 'BDT') // typed MinorUnits<'BDT'>
|
|
33
|
-
* MinorUnits(199, 'USD') // typed MinorUnits<'USD'>
|
|
34
|
-
*/
|
|
35
|
-
declare function MinorUnits<C extends string = string>(amount: number, _currency?: C): MinorUnits<C>;
|
|
36
|
-
/**
|
|
37
|
-
* A monetary amount stored as an integer in the currency's minor unit.
|
|
38
|
-
*
|
|
39
|
-
* Examples:
|
|
40
|
-
* - `{ amount: 19_99, currency: 'USD' }` — 19.99 USD (2 decimals)
|
|
41
|
-
* - `{ amount: 1_500, currency: 'JPY' }` — 1500 JPY (0 decimals)
|
|
42
|
-
* - `{ amount: 500_000, currency: 'KWD' }` — 500.000 KWD (3 decimals)
|
|
43
|
-
*
|
|
44
|
-
* Storing as integers avoids IEEE 754 rounding across arithmetic. Never store
|
|
45
|
-
* `Money.amount` as a float.
|
|
46
|
-
*/
|
|
47
|
-
interface Money {
|
|
48
|
-
readonly amount: number;
|
|
49
|
-
readonly currency: CurrencyCode | string;
|
|
50
|
-
}
|
|
51
|
-
declare class CurrencyMismatchError extends Error {
|
|
52
|
-
readonly name = "CurrencyMismatchError";
|
|
53
|
-
readonly left: string;
|
|
54
|
-
readonly right: string;
|
|
55
|
-
constructor(left: string, right: string);
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Construct {@link Money} from an integer minor-unit amount.
|
|
59
|
-
*
|
|
60
|
-
* @throws TypeError if `amount` is not a finite integer.
|
|
61
|
-
*/
|
|
62
|
-
declare function money(amount: number, currency: string): Money;
|
|
63
|
-
/**
|
|
64
|
-
* Construct {@link Money} from a major-unit number (e.g. dollars, taka) by
|
|
65
|
-
* scaling to the currency's minor unit and rounding half-away-from-zero.
|
|
66
|
-
*
|
|
67
|
-
* Uses `toPrecision(15)` to strip the trailing-bit noise that appears in
|
|
68
|
-
* multiply-then-round (e.g. `0.1 + 0.2 = 0.30000000000000004`). This is
|
|
69
|
-
* enough for the vast majority of decimals encountered in real-world pricing.
|
|
70
|
-
*
|
|
71
|
-
* **Precision caveat.** A handful of decimals cannot be represented exactly
|
|
72
|
-
* in IEEE 754 and will round to the IEEE-closest integer rather than the
|
|
73
|
-
* mathematically-expected one — e.g. `1.005` is stored as `1.00499999…` and
|
|
74
|
-
* rounds to `100` minor units, not `101`. If you need decimal-exact rounding,
|
|
75
|
-
* construct money from a string or use integer minor units directly via
|
|
76
|
-
* {@link money}.
|
|
77
|
-
*
|
|
78
|
-
* @example
|
|
79
|
-
* fromMajor(19.99, 'USD') // { amount: 1999, currency: 'USD' }
|
|
80
|
-
* fromMajor(0.1 + 0.2, 'USD') // { amount: 30, currency: 'USD' }
|
|
81
|
-
* fromMajor(1500, 'JPY') // { amount: 1500, currency: 'JPY' }
|
|
82
|
-
*/
|
|
83
|
-
declare function fromMajor(major: number, currency: string): Money;
|
|
84
|
-
/** Convert {@link Money} back to its major-unit floating-point representation. */
|
|
85
|
-
declare function toMajor(m: Money): number;
|
|
86
|
-
declare function addMoney(a: Money, b: Money): Money;
|
|
87
|
-
declare function subtractMoney(a: Money, b: Money): Money;
|
|
88
|
-
/**
|
|
89
|
-
* Multiply money by a scalar. The result is rounded to an integer number of
|
|
90
|
-
* minor units using half-away-from-zero rounding.
|
|
91
|
-
*/
|
|
92
|
-
declare function multiplyMoney(m: Money, scalar: number): Money;
|
|
93
|
-
/** Sum a list of {@link Money}. Returns zero in the given currency for empty input. */
|
|
94
|
-
declare function sumMoney(values: readonly Money[], currency: string): Money;
|
|
95
|
-
/** Strict equality (amount and currency both match). */
|
|
96
|
-
declare function equalsMoney(a: Money, b: Money): boolean;
|
|
97
|
-
/**
|
|
98
|
-
* Ordering predicate. Returns -1, 0, or 1. Throws on currency mismatch — use
|
|
99
|
-
* {@link equalsMoney} for a total equality check across currencies.
|
|
100
|
-
*/
|
|
101
|
-
declare function compareMoney(a: Money, b: Money): -1 | 0 | 1;
|
|
102
|
-
declare function isZeroMoney(m: Money): boolean;
|
|
103
|
-
declare function isPositiveMoney(m: Money): boolean;
|
|
104
|
-
declare function isNegativeMoney(m: Money): boolean;
|
|
105
|
-
declare function negateMoney(m: Money): Money;
|
|
106
|
-
declare function absMoney(m: Money): Money;
|
|
107
|
-
/**
|
|
108
|
-
* Decimal major-unit number → integer minor units by decimal exponent.
|
|
109
|
-
* Rounding: raw IEEE `Math.round` — half-up toward +∞ (so `-10.5 → -10`),
|
|
110
|
-
* documented above. Throws when the result leaves the safe-integer range
|
|
111
|
-
* (also catches `NaN`/`Infinity` inputs).
|
|
112
|
-
*
|
|
113
|
-
* @example
|
|
114
|
-
* majorToMinorUnits(10.5) // 1050
|
|
115
|
-
* majorToMinorUnits(1000, 0) // 1000 (JPY-style)
|
|
116
|
-
* majorToMinorUnits(1.005) // 100 — IEEE, see note above
|
|
117
|
-
*/
|
|
118
|
-
declare function majorToMinorUnits(major: number, minorUnitDecimals?: number): number;
|
|
119
|
-
/** Integer minor units → decimal major-unit number by decimal exponent: `1050 → 10.5`. */
|
|
120
|
-
declare function minorUnitsToMajor(minor: number, minorUnitDecimals?: number): number;
|
|
121
|
-
/**
|
|
122
|
-
* Percentage of an integer minor-unit amount → integer minor units.
|
|
123
|
-
* EXACT multiply-then-round: `Math.round((minor × ratePercent) / 100)` —
|
|
124
|
-
* one rounding point, half-up toward +∞. Handles genuinely fractional rates
|
|
125
|
-
* (QST 9.975 %) without precision loss: `percentOfMinor(20000, 9.975) === 1995`.
|
|
126
|
-
*
|
|
127
|
-
* Hosts whose domain guarantees at-most-2-decimal rates MAY snap the rate to
|
|
128
|
-
* integer basis points BEFORE calling (be-prod's `#shared/money` seam does) —
|
|
129
|
-
* that is host policy layered on top, not part of this primitive.
|
|
130
|
-
*/
|
|
131
|
-
declare function percentOfMinor(minor: number, ratePercent: number): number;
|
|
132
|
-
/**
|
|
133
|
-
* Integer minor units → plain decimal string (no currency symbol, no locale):
|
|
134
|
-
* `formatMinorUnits(10550) → "105.50"`, `formatMinorUnits(1000, 0) → "1000"`.
|
|
135
|
-
* For localized currency strings use `Intl.NumberFormat` at the display layer.
|
|
136
|
-
*/
|
|
137
|
-
declare function formatMinorUnits(minor: number, minorUnitDecimals?: number): string;
|
|
138
|
-
/** Structural type guard. */
|
|
139
|
-
declare function isMoney(value: unknown): value is Money;
|
|
140
|
-
//#endregion
|
|
1
|
+
import { T as CurrencyMismatchError, _ as negateMoney, a as compareMoney, b as sumMoney, c as fromMajor, d as isPositiveMoney, f as isZeroMoney, g as multiplyMoney, h as money, i as addMoney, l as isMoney, m as minorUnitsToMajor, n as Money, o as equalsMoney, p as majorToMinorUnits, r as absMoney, s as formatMinorUnits, t as MinorUnits, u as isNegativeMoney, v as percentOfMinor, x as toMajor, y as subtractMoney } from "./money-DPo9fLBi.mjs";
|
|
141
2
|
export { CurrencyMismatchError, MinorUnits, Money, absMoney, addMoney, compareMoney, equalsMoney, formatMinorUnits, fromMajor, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, majorToMinorUnits, minorUnitsToMajor, money, multiplyMoney, negateMoney, percentOfMinor, subtractMoney, sumMoney, toMajor };
|
package/dist/money.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { minorUnitFactor } from "./currency.mjs";
|
|
1
|
+
import { CurrencyMismatchError, currencyCode, isCurrencyCode, minorUnitFactor } from "./currency.mjs";
|
|
2
2
|
//#region src/money/money.ts
|
|
3
3
|
/**
|
|
4
4
|
* Cast a raw number to {@link MinorUnits}. Pass the currency as the second
|
|
@@ -12,26 +12,17 @@ import { minorUnitFactor } from "./currency.mjs";
|
|
|
12
12
|
function MinorUnits(amount, _currency) {
|
|
13
13
|
return amount;
|
|
14
14
|
}
|
|
15
|
-
var CurrencyMismatchError = class extends Error {
|
|
16
|
-
name = "CurrencyMismatchError";
|
|
17
|
-
left;
|
|
18
|
-
right;
|
|
19
|
-
constructor(left, right) {
|
|
20
|
-
super(`Currency mismatch: ${left} vs ${right}`);
|
|
21
|
-
this.left = left;
|
|
22
|
-
this.right = right;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
15
|
/**
|
|
26
16
|
* Construct {@link Money} from an integer minor-unit amount.
|
|
27
17
|
*
|
|
28
18
|
* @throws TypeError if `amount` is not a finite integer.
|
|
19
|
+
* @throws InvalidCurrencyCodeError if `currency` is not ISO 4217 format.
|
|
29
20
|
*/
|
|
30
21
|
function money(amount, currency) {
|
|
31
22
|
if (!Number.isFinite(amount) || !Number.isInteger(amount)) throw new TypeError(`Money.amount must be a finite integer in minor units, got ${amount}`);
|
|
32
23
|
return {
|
|
33
24
|
amount,
|
|
34
|
-
currency
|
|
25
|
+
currency: currencyCode(currency)
|
|
35
26
|
};
|
|
36
27
|
}
|
|
37
28
|
/**
|
|
@@ -60,7 +51,7 @@ function fromMajor(major, currency) {
|
|
|
60
51
|
const cleaned = Number(raw.toPrecision(15));
|
|
61
52
|
return {
|
|
62
53
|
amount: Math.round(Math.abs(cleaned)) * (cleaned < 0 ? -1 : 1),
|
|
63
|
-
currency
|
|
54
|
+
currency: currencyCode(currency)
|
|
64
55
|
};
|
|
65
56
|
}
|
|
66
57
|
/** Convert {@link Money} back to its major-unit floating-point representation. */
|
|
@@ -100,7 +91,7 @@ function multiplyMoney(m, scalar) {
|
|
|
100
91
|
function sumMoney(values, currency) {
|
|
101
92
|
return values.reduce((acc, v) => addMoney(acc, v), {
|
|
102
93
|
amount: 0,
|
|
103
|
-
currency
|
|
94
|
+
currency: currencyCode(currency)
|
|
104
95
|
});
|
|
105
96
|
}
|
|
106
97
|
/** Strict equality (amount and currency both match). */
|
|
@@ -180,7 +171,7 @@ function formatMinorUnits(minor, minorUnitDecimals = 2) {
|
|
|
180
171
|
}
|
|
181
172
|
/** Structural type guard. */
|
|
182
173
|
function isMoney(value) {
|
|
183
|
-
return typeof value === "object" && value !== null && typeof value.amount === "number" && Number.isInteger(value.amount) &&
|
|
174
|
+
return typeof value === "object" && value !== null && typeof value.amount === "number" && Number.isInteger(value.amount) && isCurrencyCode(value.currency);
|
|
184
175
|
}
|
|
185
176
|
//#endregion
|
|
186
177
|
export { CurrencyMismatchError, MinorUnits, absMoney, addMoney, compareMoney, equalsMoney, formatMinorUnits, fromMajor, isMoney, isNegativeMoney, isPositiveMoney, isZeroMoney, majorToMinorUnits, minorUnitsToMajor, money, multiplyMoney, negateMoney, percentOfMinor, subtractMoney, sumMoney, toMajor };
|
package/dist/outbox.d.mts
CHANGED
|
@@ -157,6 +157,19 @@ declare class InvalidOutboxEventError extends Error {
|
|
|
157
157
|
* `claimPending`.** If the underlying storage has corrupt rows, the
|
|
158
158
|
* store is responsible for quarantining them.
|
|
159
159
|
*/
|
|
160
|
+
/**
|
|
161
|
+
* The delivery states an outbox row can hold.
|
|
162
|
+
*
|
|
163
|
+
* Named HERE, in the contract, rather than in each store. It went unnamed while every
|
|
164
|
+
* method took an event id and none took a state — `countByStatus` is the first member
|
|
165
|
+
* that makes the vocabulary part of the interface, and two stores had already written
|
|
166
|
+
* the same three strings independently by then.
|
|
167
|
+
*
|
|
168
|
+
* `dead_letter` is terminal until an operator calls `requeue`; it is a durable statement
|
|
169
|
+
* that something is owed, not a deletion.
|
|
170
|
+
*/
|
|
171
|
+
declare const OUTBOX_STATUSES: readonly ["pending", "delivered", "dead_letter"];
|
|
172
|
+
type OutboxStatus = (typeof OUTBOX_STATUSES)[number];
|
|
160
173
|
interface OutboxStore {
|
|
161
174
|
/**
|
|
162
175
|
* Declares that `save` ENLISTS `options.session` in the caller's transaction,
|
|
@@ -242,6 +255,41 @@ interface OutboxStore {
|
|
|
242
255
|
* @returns Number of purged events.
|
|
243
256
|
*/
|
|
244
257
|
purge?(olderThanMs: number): Promise<number>;
|
|
258
|
+
/**
|
|
259
|
+
* Re-drive ONE dead-lettered event: back to `pending`, attempts reset, lease cleared,
|
|
260
|
+
* immediately visible.
|
|
261
|
+
*
|
|
262
|
+
* The other half of `fail({ deadLetter: true })`. Dead-lettering is only a durable
|
|
263
|
+
* statement that something is owed; a store that can dead-letter but not requeue leaves
|
|
264
|
+
* an operator who has fixed the root cause with no way to act on the worklist except
|
|
265
|
+
* hand-editing rows in the database.
|
|
266
|
+
*
|
|
267
|
+
* MUST be scoped to `dead_letter` — requeuing a `pending` row would reset the attempt
|
|
268
|
+
* count on an event that is merely backing off, defeating the retry policy, and
|
|
269
|
+
* requeuing a `delivered` row would republish an event every subscriber already saw.
|
|
270
|
+
*
|
|
271
|
+
* @returns `true` if a dead-lettered event was requeued, `false` if no such event
|
|
272
|
+
* exists — NOT an error. An operator re-driving a worklist races the relay, and an
|
|
273
|
+
* id that has already been dealt with is an ordinary outcome.
|
|
274
|
+
*/
|
|
275
|
+
requeue?(eventId: string): Promise<boolean>;
|
|
276
|
+
/**
|
|
277
|
+
* How many events are in `status`. The health surface behind an operator dashboard or
|
|
278
|
+
* an alert (`dead_letter > 0` means money or access may be unrecorded).
|
|
279
|
+
*/
|
|
280
|
+
countByStatus?(status: OutboxStatus): Promise<number>;
|
|
281
|
+
/**
|
|
282
|
+
* Age in ms of the OLDEST pending event, or `null` when nothing is pending.
|
|
283
|
+
*
|
|
284
|
+
* The single most useful outbox health signal, and the one a naive dashboard misses: a
|
|
285
|
+
* pending COUNT looks identical whether the queue is draining briskly or has been
|
|
286
|
+
* wedged for an hour behind one poison row. Age distinguishes them.
|
|
287
|
+
*
|
|
288
|
+
* Measured from the event's creation, not its last attempt — the question being asked
|
|
289
|
+
* is "how long has this event gone undelivered", and resetting the clock on each retry
|
|
290
|
+
* would make a permanently-failing row look permanently fresh.
|
|
291
|
+
*/
|
|
292
|
+
oldestPendingAgeMs?(): Promise<number | null>;
|
|
245
293
|
}
|
|
246
294
|
//#endregion
|
|
247
|
-
export { InvalidOutboxEventError, OutboxAcknowledgeOptions, OutboxClaimOptions, OutboxErrorInfo, OutboxFailOptions, OutboxFailureContext, OutboxFailureDecision, OutboxFailurePolicy, OutboxOwnershipError, OutboxStore, OutboxWriteOptions };
|
|
295
|
+
export { InvalidOutboxEventError, OUTBOX_STATUSES, OutboxAcknowledgeOptions, OutboxClaimOptions, OutboxErrorInfo, OutboxFailOptions, OutboxFailureContext, OutboxFailureDecision, OutboxFailurePolicy, OutboxOwnershipError, OutboxStatus, OutboxStore, OutboxWriteOptions };
|