@stll/money 0.2.0 → 0.2.2
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/dist/index.d.ts +17 -17
- package/dist/index.js +7 -3
- package/package.json +4 -4
package/dist/index.d.ts
CHANGED
|
@@ -22,14 +22,14 @@ type CentsAmount = number & {
|
|
|
22
22
|
* moving. An amount that far out is a caller defect rather than a runtime
|
|
23
23
|
* condition, like a fractional one, so it panics the same way.
|
|
24
24
|
*/
|
|
25
|
-
declare const cents: (value: number) => CentsAmount;
|
|
25
|
+
export declare const cents: (value: number) => CentsAmount;
|
|
26
26
|
/**
|
|
27
27
|
* Escape hatch for code paths that genuinely need to attach the brand
|
|
28
28
|
* without a runtime check (test fixtures, generated code). Prefer
|
|
29
29
|
* `cents()` everywhere else; reach for this only with a `// SAFETY:`
|
|
30
30
|
* comment naming why the value is already a valid minor-unit integer.
|
|
31
31
|
*/
|
|
32
|
-
declare const unsafeCents: (value: number) => CentsAmount;
|
|
32
|
+
export declare const unsafeCents: (value: number) => CentsAmount;
|
|
33
33
|
//#endregion
|
|
34
34
|
//#region src/format.d.ts
|
|
35
35
|
/**
|
|
@@ -40,7 +40,7 @@ declare const unsafeCents: (value: number) => CentsAmount;
|
|
|
40
40
|
* A malformed code makes the `Intl.NumberFormat` constructor throw, before any
|
|
41
41
|
* `?? 2` on its result could help, so the fallback has to wrap the call.
|
|
42
42
|
*/
|
|
43
|
-
declare const currencyMinorUnitDigits: (currency: string) => number;
|
|
43
|
+
export declare const currencyMinorUnitDigits: (currency: string) => number;
|
|
44
44
|
type ToMinorUnitsParams = {
|
|
45
45
|
/**
|
|
46
46
|
* The amount in major units: the decimal text a form holds, or a number.
|
|
@@ -67,7 +67,7 @@ type ToMinorUnitsParams = {
|
|
|
67
67
|
* and a result outside the safe integer range, are caller defects: gate the
|
|
68
68
|
* text with `isDecimalAmount` first.
|
|
69
69
|
*/
|
|
70
|
-
declare const toMinorUnits: (params: ToMinorUnitsParams) => CentsAmount;
|
|
70
|
+
export declare const toMinorUnits: (params: ToMinorUnitsParams) => CentsAmount;
|
|
71
71
|
/**
|
|
72
72
|
* The same conversion for text nobody has vouched for yet: null when the text
|
|
73
73
|
* is not a decimal amount, and null when the scaled value would leave the safe
|
|
@@ -77,13 +77,13 @@ declare const toMinorUnits: (params: ToMinorUnitsParams) => CentsAmount;
|
|
|
77
77
|
* past that gate calls `toMinorUnits`, which panics, because by then the
|
|
78
78
|
* decision has been made.
|
|
79
79
|
*/
|
|
80
|
-
declare const tryToMinorUnits: ({ amount, currency }: ToMinorUnitsParams) => CentsAmount | null;
|
|
80
|
+
export declare const tryToMinorUnits: ({ amount, currency }: ToMinorUnitsParams) => CentsAmount | null;
|
|
81
81
|
type ToMajorUnitsParams = {
|
|
82
82
|
amountCents: number;
|
|
83
83
|
currency: string;
|
|
84
84
|
};
|
|
85
85
|
/** The inverse: a stored amount as the major-unit number a person reads. */
|
|
86
|
-
declare const toMajorUnits: ({ amountCents, currency }: ToMajorUnitsParams) => number;
|
|
86
|
+
export declare const toMajorUnits: ({ amountCents, currency }: ToMajorUnitsParams) => number;
|
|
87
87
|
type FormatMoneyCentsParams = {
|
|
88
88
|
amountCents: number;
|
|
89
89
|
currency: string;
|
|
@@ -102,19 +102,19 @@ type FormatMoneyCentsParams = {
|
|
|
102
102
|
* showing "15.00 A1C" is wrong-looking data, which is the truth, where a thrown
|
|
103
103
|
* RangeError would take the whole board down with it.
|
|
104
104
|
*/
|
|
105
|
-
declare const formatMoneyCents: ({ amountCents, currency, locale, fractionDigits }: FormatMoneyCentsParams) => string;
|
|
105
|
+
export declare const formatMoneyCents: ({ amountCents, currency, locale, fractionDigits }: FormatMoneyCentsParams) => string;
|
|
106
106
|
//#endregion
|
|
107
107
|
//#region src/index.d.ts
|
|
108
|
-
type ProrateHourlyCentsInput = {
|
|
108
|
+
export type ProrateHourlyCentsInput = {
|
|
109
109
|
billedMinutes: number;
|
|
110
110
|
hourlyRateCents: CentsAmount;
|
|
111
111
|
};
|
|
112
|
-
declare const prorateHourlyCents: ({ billedMinutes, hourlyRateCents }: ProrateHourlyCentsInput) => CentsAmount;
|
|
113
|
-
type ApplyMarkupCentsInput = {
|
|
112
|
+
export declare const prorateHourlyCents: ({ billedMinutes, hourlyRateCents }: ProrateHourlyCentsInput) => CentsAmount;
|
|
113
|
+
export type ApplyMarkupCentsInput = {
|
|
114
114
|
amountCents: CentsAmount;
|
|
115
115
|
markupPercent: number;
|
|
116
116
|
};
|
|
117
|
-
declare const applyMarkupCents: ({ amountCents, markupPercent }: ApplyMarkupCentsInput) => CentsAmount;
|
|
117
|
+
export declare const applyMarkupCents: ({ amountCents, markupPercent }: ApplyMarkupCentsInput) => CentsAmount;
|
|
118
118
|
declare const __currency: unique symbol;
|
|
119
119
|
/**
|
|
120
120
|
* A `CentsAmount` additionally branded with its ISO 4217-ish currency code
|
|
@@ -128,7 +128,7 @@ declare const __currency: unique symbol;
|
|
|
128
128
|
* currency code is a plain string carried alongside it, so there is no
|
|
129
129
|
* boundary that needs to skip it.
|
|
130
130
|
*/
|
|
131
|
-
type CurrencyCents<C extends string = string> = CentsAmount & {
|
|
131
|
+
export type CurrencyCents<C extends string = string> = CentsAmount & {
|
|
132
132
|
readonly [__currency]: C;
|
|
133
133
|
};
|
|
134
134
|
/**
|
|
@@ -137,7 +137,7 @@ type CurrencyCents<C extends string = string> = CentsAmount & {
|
|
|
137
137
|
* from the literal `currency` argument (e.g. `currencyCents("USD", 100)`
|
|
138
138
|
* infers `CurrencyCents<"USD">`).
|
|
139
139
|
*/
|
|
140
|
-
declare const currencyCents: <C extends string>(currency: C, amount: number) => CurrencyCents<C>;
|
|
140
|
+
export declare const currencyCents: <C extends string>(currency: C, amount: number) => CurrencyCents<C>;
|
|
141
141
|
type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends ((x: infer I) => void) ? I : never;
|
|
142
142
|
type IsSingletonCurrency<C extends string> = string extends C ? never : [C] extends [UnionToIntersection<C>] ? C : never;
|
|
143
143
|
/**
|
|
@@ -161,7 +161,7 @@ type IsSingletonCurrency<C extends string> = string extends C ? never : [C] exte
|
|
|
161
161
|
* instead, which buckets by currency at runtime and is the runtime-correct
|
|
162
162
|
* tool for that case.
|
|
163
163
|
*/
|
|
164
|
-
declare const addCents: <A extends string, B extends A = A>(a: IsSingletonCurrency<A> extends never ? never : CurrencyCents<A>, b: IsSingletonCurrency<B> extends never ? never : CurrencyCents<B>) => CurrencyCents<A>;
|
|
164
|
+
export declare const addCents: <A extends string, B extends A = A>(a: IsSingletonCurrency<A> extends never ? never : CurrencyCents<A>, b: IsSingletonCurrency<B> extends never ? never : CurrencyCents<B>) => CurrencyCents<A>;
|
|
165
165
|
/**
|
|
166
166
|
* Per-currency accumulator for aggregating money across rows that may carry
|
|
167
167
|
* different currencies (e.g. time entries across matters, expenses across
|
|
@@ -182,11 +182,11 @@ declare const addCents: <A extends string, B extends A = A>(a: IsSingletonCurren
|
|
|
182
182
|
* the same "never sum across currencies" invariant at runtime via the
|
|
183
183
|
* per-currency `Map`.
|
|
184
184
|
*/
|
|
185
|
-
type MoneyTotalsEntry = {
|
|
185
|
+
export type MoneyTotalsEntry = {
|
|
186
186
|
currency: string;
|
|
187
187
|
amountCents: CentsAmount;
|
|
188
188
|
};
|
|
189
|
-
declare class MoneyTotals {
|
|
189
|
+
export declare class MoneyTotals {
|
|
190
190
|
#private;
|
|
191
191
|
/** Add `amountCents` to the running total for `currency`. */
|
|
192
192
|
add(currency: string, amountCents: CentsAmount): void;
|
|
@@ -199,4 +199,4 @@ declare class MoneyTotals {
|
|
|
199
199
|
entries(): MoneyTotalsEntry[];
|
|
200
200
|
}
|
|
201
201
|
//#endregion
|
|
202
|
-
export {
|
|
202
|
+
export type { CentsAmount, FormatMoneyCentsParams, ToMajorUnitsParams, ToMinorUnitsParams };
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,8 @@ import { Result, panic } from "better-result";
|
|
|
7
7
|
* conversion helpers there return a `CentsAmount`, and a module the package
|
|
8
8
|
* entry re-exports cannot import back from that entry without a cycle.
|
|
9
9
|
*/
|
|
10
|
+
/** The brand is nominal: every safe integer is a valid minor-unit amount. */
|
|
11
|
+
const isMinorUnitAmount = (value) => Number.isSafeInteger(value);
|
|
10
12
|
/**
|
|
11
13
|
* Construct a CentsAmount from a value already known to be in minor
|
|
12
14
|
* units. Use at boundaries where the input is validated as an integer
|
|
@@ -20,7 +22,7 @@ import { Result, panic } from "better-result";
|
|
|
20
22
|
* condition, like a fractional one, so it panics the same way.
|
|
21
23
|
*/
|
|
22
24
|
const cents = (value) => {
|
|
23
|
-
if (!
|
|
25
|
+
if (!isMinorUnitAmount(value)) return panic(`cents(${value}): money values must be safe integer minor units`);
|
|
24
26
|
return value;
|
|
25
27
|
};
|
|
26
28
|
/**
|
|
@@ -217,6 +219,7 @@ const applyMarkupCents = ({ amountCents, markupPercent }) => {
|
|
|
217
219
|
function assertNonNegativeInteger(name, value) {
|
|
218
220
|
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0) panic(`${name} must be a finite non-negative integer`);
|
|
219
221
|
}
|
|
222
|
+
const isCurrencyCents = (amount, currency) => currency !== "" && isMinorUnitAmount(amount);
|
|
220
223
|
/**
|
|
221
224
|
* Construct a `CurrencyCents<C>` from a currency code and a minor-unit
|
|
222
225
|
* amount. The only producer of `CurrencyCents`; downstream code narrows `C`
|
|
@@ -225,7 +228,8 @@ function assertNonNegativeInteger(name, value) {
|
|
|
225
228
|
*/
|
|
226
229
|
const currencyCents = (currency, amount) => {
|
|
227
230
|
if (!currency) return panic("currencyCents(): currency must be a non-empty code");
|
|
228
|
-
|
|
231
|
+
const minorUnits = cents(amount);
|
|
232
|
+
return isCurrencyCents(minorUnits, currency) ? minorUnits : panic("currencyCents(): currency must be a non-empty code");
|
|
229
233
|
};
|
|
230
234
|
/**
|
|
231
235
|
* Add two `CurrencyCents` amounts of the SAME currency. The second
|
|
@@ -264,7 +268,7 @@ var MoneyTotals = class {
|
|
|
264
268
|
* `localeCompare`, so ordering does not vary with the runtime's locale.
|
|
265
269
|
*/
|
|
266
270
|
entries() {
|
|
267
|
-
return [...this.#totals.keys()].
|
|
271
|
+
return [...this.#totals.keys()].toSorted().map((currency) => ({
|
|
268
272
|
currency,
|
|
269
273
|
amountCents: this.#totals.get(currency) ?? cents(0)
|
|
270
274
|
}));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/money",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Branded minor-unit monetary amounts and currency-safe billing arithmetic.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"billing",
|
|
@@ -40,18 +40,18 @@
|
|
|
40
40
|
"pack:dry-run": "bun pm pack --dry-run",
|
|
41
41
|
"test": "bun test src",
|
|
42
42
|
"typecheck": "bun ../../packages/scripts/src/tsc-native.ts --noEmit",
|
|
43
|
-
"lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --
|
|
43
|
+
"lint": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --report-unused-disable-directives-severity=error --type-aware packages/money",
|
|
44
44
|
"lint:fix": "cd ../.. && bun --bun oxlint -c oxlint.config.ts --type-aware --fix packages/money",
|
|
45
45
|
"format": "bun ../../scripts/run-oxfmt.ts .",
|
|
46
46
|
"prepack": "bun run build"
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
|
-
"better-result": "3.0.
|
|
49
|
+
"better-result": "3.0.1"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@stll/typescript-config": "0.0.0",
|
|
53
53
|
"bun-types": "1.4.2",
|
|
54
|
-
"tsdown": "0.
|
|
54
|
+
"tsdown": "0.23.0"
|
|
55
55
|
},
|
|
56
56
|
"main": "./dist/index.js",
|
|
57
57
|
"types": "./dist/index.d.ts"
|