@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 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 { ApplyMarkupCentsInput, type CentsAmount, CurrencyCents, type FormatMoneyCentsParams, MoneyTotals, MoneyTotalsEntry, ProrateHourlyCentsInput, type ToMajorUnitsParams, type ToMinorUnitsParams, addCents, applyMarkupCents, cents, currencyCents, currencyMinorUnitDigits, formatMoneyCents, prorateHourlyCents, toMajorUnits, toMinorUnits, tryToMinorUnits, unsafeCents };
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 (!Number.isSafeInteger(value)) return panic(`cents(${value}): money values must be safe integer minor units`);
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
- return cents(amount);
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()].sort().map((currency) => ({
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.0",
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 --deny-warnings --type-aware packages/money",
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.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.22.14"
54
+ "tsdown": "0.23.0"
55
55
  },
56
56
  "main": "./dist/index.js",
57
57
  "types": "./dist/index.d.ts"