@storepass/money 0.1.4 → 0.2.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 +43 -0
- package/dist/currency.d.ts +83 -1
- package/dist/index.cjs +116 -38
- package/dist/index.d.ts +1 -1
- package/dist/{index.js → index.mjs} +98 -30
- package/dist/money.d.ts +17 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -111,6 +111,49 @@ const json = m.toJSON();
|
|
|
111
111
|
|
|
112
112
|
`amount` is always in minor units.
|
|
113
113
|
|
|
114
|
+
### Display formatting
|
|
115
|
+
|
|
116
|
+
`Money#format` wraps `Intl.NumberFormat`, honoring the currency's
|
|
117
|
+
`decimalPlaces` and gracefully degrading for non-ISO codes.
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
Money.fromNumber(Currency.USD, 1234.5).format("en-US"); // "$1,234.50"
|
|
121
|
+
Money.fromNumber(Currency.EUR, 1234.56).format("de-DE"); // "1.234,56 €"
|
|
122
|
+
Money.fromNumber(Currency.JPY, 1500).format("en-US"); // "¥1,500"
|
|
123
|
+
Money.fromNumber(Currency.BTC, 0.12345678).format("en-US"); // "BTC 0.12345678"
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
For non-ISO codes, you can supply a `displayCode` so `Intl` still renders a
|
|
127
|
+
proper currency symbol while arithmetic precision is preserved separately:
|
|
128
|
+
|
|
129
|
+
```ts
|
|
130
|
+
const USDH = defineCurrency("USDH", 3, { displayCode: "USD" });
|
|
131
|
+
Money.fromNumber(USDH, 12.345).format("en-US"); // "$12.345"
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## ISO 4217 compatibility
|
|
135
|
+
|
|
136
|
+
Standard currencies in `Currency` carry `numericCode` and `iso4217: true`.
|
|
137
|
+
Custom assets (e.g., `BTC`, `USDH`) are flagged `iso4217: false`. When
|
|
138
|
+
defining your own, pass the metadata explicitly:
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
const SLE = defineCurrency("SLE", 2, { numericCode: 925, iso4217: true });
|
|
142
|
+
const XAU = defineCurrency("XAU", 4); // non-monetary, no ISO flags
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
`defineCurrency` validates `iso4217` codes against `/^[A-Z]{3}$/` and
|
|
146
|
+
requires a numeric code in `0..999`.
|
|
147
|
+
|
|
148
|
+
### Staying compliant
|
|
149
|
+
|
|
150
|
+
ISO 4217 is amended a few times per year by SIX Interbank Clearing
|
|
151
|
+
(deprecations, redenominations, occasional additions). The hardcoded list
|
|
152
|
+
in [src/currency.ts](./src/currency.ts) tracks a pinned revision (see the
|
|
153
|
+
header comment). To stay current, periodically diff against the
|
|
154
|
+
[official XML list](https://www.six-group.com/en/products-services/financial-information/data-standards.html)
|
|
155
|
+
and update both `decimalPlaces` and `numericCode` for any drift.
|
|
156
|
+
|
|
114
157
|
## Notes and Guarantees
|
|
115
158
|
|
|
116
159
|
- Currency mismatch operations throw (for example, adding USD to EUR).
|
package/dist/currency.d.ts
CHANGED
|
@@ -1,121 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency metadata.
|
|
3
|
+
*
|
|
4
|
+
* For ISO 4217 currencies, `iso4217` is `true` and `numericCode` is the
|
|
5
|
+
* official 3-digit numeric code. `displayCode` is an optional override that
|
|
6
|
+
* `Intl.NumberFormat` should use when the primary `code` is non-standard
|
|
7
|
+
* (e.g., legacy systems calling Bitcoin "BTC" instead of the unofficial "XBT").
|
|
8
|
+
*
|
|
9
|
+
* Standard currencies in this file follow ISO 4217 as of revision 173
|
|
10
|
+
* (published 2024-06). Run `bun run scripts/sync-iso4217.ts` to refresh.
|
|
11
|
+
*/
|
|
1
12
|
export interface CurrencyDefinition {
|
|
13
|
+
/** Alpha code (typically ISO 4217 alpha-3, but free-form for custom assets). */
|
|
2
14
|
code: string;
|
|
15
|
+
/** Minor-unit precision used for internal arithmetic. */
|
|
3
16
|
decimalPlaces: number;
|
|
17
|
+
/** ISO 4217 numeric code (0-999). Omitted for non-standard assets. */
|
|
18
|
+
numericCode?: number;
|
|
19
|
+
/** `true` when `code` is an officially registered ISO 4217 alpha-3 code. */
|
|
20
|
+
iso4217?: boolean;
|
|
21
|
+
/**
|
|
22
|
+
* Optional alpha-3 used for `Intl.NumberFormat` display. Falls back to `code`.
|
|
23
|
+
* Useful for non-ISO codes that have a registered alias.
|
|
24
|
+
*/
|
|
25
|
+
displayCode?: string;
|
|
4
26
|
}
|
|
5
27
|
export declare const Currency: {
|
|
6
28
|
readonly USD: {
|
|
7
29
|
readonly code: "USD";
|
|
8
30
|
readonly decimalPlaces: 2;
|
|
31
|
+
readonly numericCode: 840;
|
|
32
|
+
readonly iso4217: true;
|
|
9
33
|
};
|
|
10
34
|
readonly USDH: {
|
|
11
35
|
readonly code: "USDH";
|
|
12
36
|
readonly decimalPlaces: 3;
|
|
37
|
+
readonly iso4217: false;
|
|
38
|
+
readonly displayCode: "USD";
|
|
13
39
|
};
|
|
14
40
|
readonly EUR: {
|
|
15
41
|
readonly code: "EUR";
|
|
16
42
|
readonly decimalPlaces: 2;
|
|
43
|
+
readonly numericCode: 978;
|
|
44
|
+
readonly iso4217: true;
|
|
17
45
|
};
|
|
18
46
|
readonly GBP: {
|
|
19
47
|
readonly code: "GBP";
|
|
20
48
|
readonly decimalPlaces: 2;
|
|
49
|
+
readonly numericCode: 826;
|
|
50
|
+
readonly iso4217: true;
|
|
21
51
|
};
|
|
22
52
|
readonly JPY: {
|
|
23
53
|
readonly code: "JPY";
|
|
24
54
|
readonly decimalPlaces: 0;
|
|
55
|
+
readonly numericCode: 392;
|
|
56
|
+
readonly iso4217: true;
|
|
25
57
|
};
|
|
26
58
|
readonly CAD: {
|
|
27
59
|
readonly code: "CAD";
|
|
28
60
|
readonly decimalPlaces: 2;
|
|
61
|
+
readonly numericCode: 124;
|
|
62
|
+
readonly iso4217: true;
|
|
29
63
|
};
|
|
30
64
|
readonly AUD: {
|
|
31
65
|
readonly code: "AUD";
|
|
32
66
|
readonly decimalPlaces: 2;
|
|
67
|
+
readonly numericCode: 36;
|
|
68
|
+
readonly iso4217: true;
|
|
33
69
|
};
|
|
34
70
|
readonly NZD: {
|
|
35
71
|
readonly code: "NZD";
|
|
36
72
|
readonly decimalPlaces: 2;
|
|
73
|
+
readonly numericCode: 554;
|
|
74
|
+
readonly iso4217: true;
|
|
37
75
|
};
|
|
38
76
|
readonly CHF: {
|
|
39
77
|
readonly code: "CHF";
|
|
40
78
|
readonly decimalPlaces: 2;
|
|
79
|
+
readonly numericCode: 756;
|
|
80
|
+
readonly iso4217: true;
|
|
41
81
|
};
|
|
42
82
|
readonly SEK: {
|
|
43
83
|
readonly code: "SEK";
|
|
44
84
|
readonly decimalPlaces: 2;
|
|
85
|
+
readonly numericCode: 752;
|
|
86
|
+
readonly iso4217: true;
|
|
45
87
|
};
|
|
46
88
|
readonly NOK: {
|
|
47
89
|
readonly code: "NOK";
|
|
48
90
|
readonly decimalPlaces: 2;
|
|
91
|
+
readonly numericCode: 578;
|
|
92
|
+
readonly iso4217: true;
|
|
49
93
|
};
|
|
50
94
|
readonly DKK: {
|
|
51
95
|
readonly code: "DKK";
|
|
52
96
|
readonly decimalPlaces: 2;
|
|
97
|
+
readonly numericCode: 208;
|
|
98
|
+
readonly iso4217: true;
|
|
53
99
|
};
|
|
54
100
|
readonly PLN: {
|
|
55
101
|
readonly code: "PLN";
|
|
56
102
|
readonly decimalPlaces: 2;
|
|
103
|
+
readonly numericCode: 985;
|
|
104
|
+
readonly iso4217: true;
|
|
57
105
|
};
|
|
58
106
|
readonly CZK: {
|
|
59
107
|
readonly code: "CZK";
|
|
60
108
|
readonly decimalPlaces: 2;
|
|
109
|
+
readonly numericCode: 203;
|
|
110
|
+
readonly iso4217: true;
|
|
61
111
|
};
|
|
62
112
|
readonly HUF: {
|
|
63
113
|
readonly code: "HUF";
|
|
64
114
|
readonly decimalPlaces: 2;
|
|
115
|
+
readonly numericCode: 348;
|
|
116
|
+
readonly iso4217: true;
|
|
65
117
|
};
|
|
66
118
|
readonly BRL: {
|
|
67
119
|
readonly code: "BRL";
|
|
68
120
|
readonly decimalPlaces: 2;
|
|
121
|
+
readonly numericCode: 986;
|
|
122
|
+
readonly iso4217: true;
|
|
69
123
|
};
|
|
70
124
|
readonly MXN: {
|
|
71
125
|
readonly code: "MXN";
|
|
72
126
|
readonly decimalPlaces: 2;
|
|
127
|
+
readonly numericCode: 484;
|
|
128
|
+
readonly iso4217: true;
|
|
73
129
|
};
|
|
74
130
|
readonly ARS: {
|
|
75
131
|
readonly code: "ARS";
|
|
76
132
|
readonly decimalPlaces: 2;
|
|
133
|
+
readonly numericCode: 32;
|
|
134
|
+
readonly iso4217: true;
|
|
77
135
|
};
|
|
78
136
|
readonly CLP: {
|
|
79
137
|
readonly code: "CLP";
|
|
80
138
|
readonly decimalPlaces: 0;
|
|
139
|
+
readonly numericCode: 152;
|
|
140
|
+
readonly iso4217: true;
|
|
81
141
|
};
|
|
82
142
|
readonly INR: {
|
|
83
143
|
readonly code: "INR";
|
|
84
144
|
readonly decimalPlaces: 2;
|
|
145
|
+
readonly numericCode: 356;
|
|
146
|
+
readonly iso4217: true;
|
|
85
147
|
};
|
|
86
148
|
readonly SGD: {
|
|
87
149
|
readonly code: "SGD";
|
|
88
150
|
readonly decimalPlaces: 2;
|
|
151
|
+
readonly numericCode: 702;
|
|
152
|
+
readonly iso4217: true;
|
|
89
153
|
};
|
|
90
154
|
readonly HKD: {
|
|
91
155
|
readonly code: "HKD";
|
|
92
156
|
readonly decimalPlaces: 2;
|
|
157
|
+
readonly numericCode: 344;
|
|
158
|
+
readonly iso4217: true;
|
|
93
159
|
};
|
|
94
160
|
readonly KRW: {
|
|
95
161
|
readonly code: "KRW";
|
|
96
162
|
readonly decimalPlaces: 0;
|
|
163
|
+
readonly numericCode: 410;
|
|
164
|
+
readonly iso4217: true;
|
|
97
165
|
};
|
|
98
166
|
readonly CNY: {
|
|
99
167
|
readonly code: "CNY";
|
|
100
168
|
readonly decimalPlaces: 2;
|
|
169
|
+
readonly numericCode: 156;
|
|
170
|
+
readonly iso4217: true;
|
|
101
171
|
};
|
|
102
172
|
readonly ZAR: {
|
|
103
173
|
readonly code: "ZAR";
|
|
104
174
|
readonly decimalPlaces: 2;
|
|
175
|
+
readonly numericCode: 710;
|
|
176
|
+
readonly iso4217: true;
|
|
105
177
|
};
|
|
106
178
|
readonly AED: {
|
|
107
179
|
readonly code: "AED";
|
|
108
180
|
readonly decimalPlaces: 2;
|
|
181
|
+
readonly numericCode: 784;
|
|
182
|
+
readonly iso4217: true;
|
|
109
183
|
};
|
|
110
184
|
readonly SAR: {
|
|
111
185
|
readonly code: "SAR";
|
|
112
186
|
readonly decimalPlaces: 2;
|
|
187
|
+
readonly numericCode: 682;
|
|
188
|
+
readonly iso4217: true;
|
|
113
189
|
};
|
|
114
190
|
readonly BTC: {
|
|
115
191
|
readonly code: "BTC";
|
|
116
192
|
readonly decimalPlaces: 8;
|
|
193
|
+
readonly iso4217: false;
|
|
117
194
|
};
|
|
118
195
|
};
|
|
119
196
|
export type CurrencyCode = keyof typeof Currency;
|
|
120
197
|
export type CurrencyType = (typeof Currency)[CurrencyCode];
|
|
121
|
-
export
|
|
198
|
+
export interface DefineCurrencyOptions {
|
|
199
|
+
numericCode?: number;
|
|
200
|
+
iso4217?: boolean;
|
|
201
|
+
displayCode?: string;
|
|
202
|
+
}
|
|
203
|
+
export declare function defineCurrency(code: string, decimalPlaces: number, options?: DefineCurrencyOptions): CurrencyDefinition;
|
package/dist/index.cjs
CHANGED
|
@@ -2,27 +2,37 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
3
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
4
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
|
|
5
|
+
function __accessProp(key) {
|
|
6
|
+
return this[key];
|
|
7
|
+
}
|
|
6
8
|
var __toCommonJS = (from) => {
|
|
7
|
-
var entry = __moduleCache.get(from), desc;
|
|
9
|
+
var entry = (__moduleCache ??= new WeakMap).get(from), desc;
|
|
8
10
|
if (entry)
|
|
9
11
|
return entry;
|
|
10
12
|
entry = __defProp({}, "__esModule", { value: true });
|
|
11
|
-
if (from && typeof from === "object" || typeof from === "function")
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (var key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(entry, key))
|
|
16
|
+
__defProp(entry, key, {
|
|
17
|
+
get: __accessProp.bind(from, key),
|
|
18
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
19
|
+
});
|
|
20
|
+
}
|
|
16
21
|
__moduleCache.set(from, entry);
|
|
17
22
|
return entry;
|
|
18
23
|
};
|
|
24
|
+
var __moduleCache;
|
|
25
|
+
var __returnValue = (v) => v;
|
|
26
|
+
function __exportSetter(name, newValue) {
|
|
27
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
28
|
+
}
|
|
19
29
|
var __export = (target, all) => {
|
|
20
30
|
for (var name in all)
|
|
21
31
|
__defProp(target, name, {
|
|
22
32
|
get: all[name],
|
|
23
33
|
enumerable: true,
|
|
24
34
|
configurable: true,
|
|
25
|
-
set: (
|
|
35
|
+
set: __exportSetter.bind(all, name)
|
|
26
36
|
});
|
|
27
37
|
};
|
|
28
38
|
|
|
@@ -37,43 +47,67 @@ module.exports = __toCommonJS(exports_src);
|
|
|
37
47
|
|
|
38
48
|
// src/currency.ts
|
|
39
49
|
var Currency = {
|
|
40
|
-
USD: { code: "USD", decimalPlaces: 2 },
|
|
41
|
-
USDH: { code: "USDH", decimalPlaces: 3 },
|
|
42
|
-
EUR: { code: "EUR", decimalPlaces: 2 },
|
|
43
|
-
GBP: { code: "GBP", decimalPlaces: 2 },
|
|
44
|
-
JPY: { code: "JPY", decimalPlaces: 0 },
|
|
45
|
-
CAD: { code: "CAD", decimalPlaces: 2 },
|
|
46
|
-
AUD: { code: "AUD", decimalPlaces: 2 },
|
|
47
|
-
NZD: { code: "NZD", decimalPlaces: 2 },
|
|
48
|
-
CHF: { code: "CHF", decimalPlaces: 2 },
|
|
49
|
-
SEK: { code: "SEK", decimalPlaces: 2 },
|
|
50
|
-
NOK: { code: "NOK", decimalPlaces: 2 },
|
|
51
|
-
DKK: { code: "DKK", decimalPlaces: 2 },
|
|
52
|
-
PLN: { code: "PLN", decimalPlaces: 2 },
|
|
53
|
-
CZK: { code: "CZK", decimalPlaces: 2 },
|
|
54
|
-
HUF: { code: "HUF", decimalPlaces: 2 },
|
|
55
|
-
BRL: { code: "BRL", decimalPlaces: 2 },
|
|
56
|
-
MXN: { code: "MXN", decimalPlaces: 2 },
|
|
57
|
-
ARS: { code: "ARS", decimalPlaces: 2 },
|
|
58
|
-
CLP: { code: "CLP", decimalPlaces: 0 },
|
|
59
|
-
INR: { code: "INR", decimalPlaces: 2 },
|
|
60
|
-
SGD: { code: "SGD", decimalPlaces: 2 },
|
|
61
|
-
HKD: { code: "HKD", decimalPlaces: 2 },
|
|
62
|
-
KRW: { code: "KRW", decimalPlaces: 0 },
|
|
63
|
-
CNY: { code: "CNY", decimalPlaces: 2 },
|
|
64
|
-
ZAR: { code: "ZAR", decimalPlaces: 2 },
|
|
65
|
-
AED: { code: "AED", decimalPlaces: 2 },
|
|
66
|
-
SAR: { code: "SAR", decimalPlaces: 2 },
|
|
67
|
-
BTC: { code: "BTC", decimalPlaces: 8 }
|
|
50
|
+
USD: { code: "USD", decimalPlaces: 2, numericCode: 840, iso4217: true },
|
|
51
|
+
USDH: { code: "USDH", decimalPlaces: 3, iso4217: false, displayCode: "USD" },
|
|
52
|
+
EUR: { code: "EUR", decimalPlaces: 2, numericCode: 978, iso4217: true },
|
|
53
|
+
GBP: { code: "GBP", decimalPlaces: 2, numericCode: 826, iso4217: true },
|
|
54
|
+
JPY: { code: "JPY", decimalPlaces: 0, numericCode: 392, iso4217: true },
|
|
55
|
+
CAD: { code: "CAD", decimalPlaces: 2, numericCode: 124, iso4217: true },
|
|
56
|
+
AUD: { code: "AUD", decimalPlaces: 2, numericCode: 36, iso4217: true },
|
|
57
|
+
NZD: { code: "NZD", decimalPlaces: 2, numericCode: 554, iso4217: true },
|
|
58
|
+
CHF: { code: "CHF", decimalPlaces: 2, numericCode: 756, iso4217: true },
|
|
59
|
+
SEK: { code: "SEK", decimalPlaces: 2, numericCode: 752, iso4217: true },
|
|
60
|
+
NOK: { code: "NOK", decimalPlaces: 2, numericCode: 578, iso4217: true },
|
|
61
|
+
DKK: { code: "DKK", decimalPlaces: 2, numericCode: 208, iso4217: true },
|
|
62
|
+
PLN: { code: "PLN", decimalPlaces: 2, numericCode: 985, iso4217: true },
|
|
63
|
+
CZK: { code: "CZK", decimalPlaces: 2, numericCode: 203, iso4217: true },
|
|
64
|
+
HUF: { code: "HUF", decimalPlaces: 2, numericCode: 348, iso4217: true },
|
|
65
|
+
BRL: { code: "BRL", decimalPlaces: 2, numericCode: 986, iso4217: true },
|
|
66
|
+
MXN: { code: "MXN", decimalPlaces: 2, numericCode: 484, iso4217: true },
|
|
67
|
+
ARS: { code: "ARS", decimalPlaces: 2, numericCode: 32, iso4217: true },
|
|
68
|
+
CLP: { code: "CLP", decimalPlaces: 0, numericCode: 152, iso4217: true },
|
|
69
|
+
INR: { code: "INR", decimalPlaces: 2, numericCode: 356, iso4217: true },
|
|
70
|
+
SGD: { code: "SGD", decimalPlaces: 2, numericCode: 702, iso4217: true },
|
|
71
|
+
HKD: { code: "HKD", decimalPlaces: 2, numericCode: 344, iso4217: true },
|
|
72
|
+
KRW: { code: "KRW", decimalPlaces: 0, numericCode: 410, iso4217: true },
|
|
73
|
+
CNY: { code: "CNY", decimalPlaces: 2, numericCode: 156, iso4217: true },
|
|
74
|
+
ZAR: { code: "ZAR", decimalPlaces: 2, numericCode: 710, iso4217: true },
|
|
75
|
+
AED: { code: "AED", decimalPlaces: 2, numericCode: 784, iso4217: true },
|
|
76
|
+
SAR: { code: "SAR", decimalPlaces: 2, numericCode: 682, iso4217: true },
|
|
77
|
+
BTC: { code: "BTC", decimalPlaces: 8, iso4217: false }
|
|
68
78
|
};
|
|
69
|
-
|
|
79
|
+
var ISO_ALPHA3 = /^[A-Z]{3}$/;
|
|
80
|
+
function defineCurrency(code, decimalPlaces, options = {}) {
|
|
70
81
|
if (!code || typeof code !== "string") {
|
|
71
82
|
throw new Error("Currency code must be a non-empty string");
|
|
72
83
|
}
|
|
73
84
|
if (!Number.isInteger(decimalPlaces) || decimalPlaces < 0) {
|
|
74
85
|
throw new Error("Decimal places must be a non-negative integer");
|
|
75
86
|
}
|
|
76
|
-
|
|
87
|
+
const { numericCode, iso4217, displayCode } = options;
|
|
88
|
+
if (iso4217 === true) {
|
|
89
|
+
if (!ISO_ALPHA3.test(code)) {
|
|
90
|
+
throw new Error(`ISO 4217 currency code must be three uppercase letters: got "${code}"`);
|
|
91
|
+
}
|
|
92
|
+
if (numericCode === undefined || !Number.isInteger(numericCode) || numericCode < 0 || numericCode > 999) {
|
|
93
|
+
throw new Error("ISO 4217 currencies require an integer numericCode in 0..999");
|
|
94
|
+
}
|
|
95
|
+
} else if (numericCode !== undefined) {
|
|
96
|
+
if (!Number.isInteger(numericCode) || numericCode < 0 || numericCode > 999) {
|
|
97
|
+
throw new Error("numericCode must be an integer in 0..999");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (displayCode !== undefined && !ISO_ALPHA3.test(displayCode)) {
|
|
101
|
+
throw new Error(`displayCode must be three uppercase letters: got "${displayCode}"`);
|
|
102
|
+
}
|
|
103
|
+
const def = { code, decimalPlaces };
|
|
104
|
+
if (numericCode !== undefined)
|
|
105
|
+
def.numericCode = numericCode;
|
|
106
|
+
if (iso4217 !== undefined)
|
|
107
|
+
def.iso4217 = iso4217;
|
|
108
|
+
if (displayCode !== undefined)
|
|
109
|
+
def.displayCode = displayCode;
|
|
110
|
+
return Object.freeze(def);
|
|
77
111
|
}
|
|
78
112
|
// src/money.ts
|
|
79
113
|
var INTERNAL_PRECISION = 20;
|
|
@@ -156,6 +190,27 @@ function assertSafeResult(value) {
|
|
|
156
190
|
throw new Error("Result too large: exceeds safe integer range");
|
|
157
191
|
}
|
|
158
192
|
}
|
|
193
|
+
function minorToDecimalString(minor, decimalPlaces) {
|
|
194
|
+
const negative = minor < 0n;
|
|
195
|
+
const absMinor = negative ? -minor : minor;
|
|
196
|
+
if (decimalPlaces === 0) {
|
|
197
|
+
return `${negative ? "-" : ""}${absMinor}`;
|
|
198
|
+
}
|
|
199
|
+
const scale = 10n ** BigInt(decimalPlaces);
|
|
200
|
+
const whole = absMinor / scale;
|
|
201
|
+
const fraction = (absMinor % scale).toString().padStart(decimalPlaces, "0");
|
|
202
|
+
return `${negative ? "-" : ""}${whole}.${fraction}`;
|
|
203
|
+
}
|
|
204
|
+
function resolveFractionDigits(decimalPlaces, options) {
|
|
205
|
+
const { minimumFractionDigits, maximumFractionDigits } = options;
|
|
206
|
+
return {
|
|
207
|
+
minimumFractionDigits: minimumFractionDigits ?? (maximumFractionDigits === undefined ? decimalPlaces : Math.min(decimalPlaces, maximumFractionDigits)),
|
|
208
|
+
maximumFractionDigits: maximumFractionDigits ?? (minimumFractionDigits === undefined ? decimalPlaces : Math.max(decimalPlaces, minimumFractionDigits))
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function formatExactDecimal(format, value) {
|
|
212
|
+
return format.format(value);
|
|
213
|
+
}
|
|
159
214
|
|
|
160
215
|
class Money {
|
|
161
216
|
#minor;
|
|
@@ -420,4 +475,27 @@ class Money {
|
|
|
420
475
|
currency: this.#currency.code
|
|
421
476
|
};
|
|
422
477
|
}
|
|
478
|
+
format(locales, options = {}) {
|
|
479
|
+
const value = minorToDecimalString(this.#minor, this.#currency.decimalPlaces);
|
|
480
|
+
const dp = this.#currency.decimalPlaces;
|
|
481
|
+
const displayCode = this.#currency.displayCode ?? this.#currency.code;
|
|
482
|
+
const canUseCurrencyStyle = this.#currency.iso4217 === true || this.#currency.displayCode !== undefined;
|
|
483
|
+
const fractionDigits = resolveFractionDigits(dp, options);
|
|
484
|
+
if (canUseCurrencyStyle) {
|
|
485
|
+
try {
|
|
486
|
+
return formatExactDecimal(new Intl.NumberFormat(locales, {
|
|
487
|
+
...options,
|
|
488
|
+
...fractionDigits,
|
|
489
|
+
style: "currency",
|
|
490
|
+
currency: displayCode
|
|
491
|
+
}), value);
|
|
492
|
+
} catch {}
|
|
493
|
+
}
|
|
494
|
+
const number = formatExactDecimal(new Intl.NumberFormat(locales, {
|
|
495
|
+
...options,
|
|
496
|
+
...fractionDigits,
|
|
497
|
+
style: "decimal"
|
|
498
|
+
}), value);
|
|
499
|
+
return `${this.#currency.code} ${number}`;
|
|
500
|
+
}
|
|
423
501
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { Currency, type CurrencyCode, type CurrencyDefinition, type CurrencyType, defineCurrency, } from "./currency";
|
|
1
|
+
export { Currency, type CurrencyCode, type CurrencyDefinition, type CurrencyType, type DefineCurrencyOptions, defineCurrency, } from "./currency";
|
|
2
2
|
export { type FromNumberOptions, Money, type RoundingMode } from "./money";
|
|
@@ -1,42 +1,66 @@
|
|
|
1
1
|
// src/currency.ts
|
|
2
2
|
var Currency = {
|
|
3
|
-
USD: { code: "USD", decimalPlaces: 2 },
|
|
4
|
-
USDH: { code: "USDH", decimalPlaces: 3 },
|
|
5
|
-
EUR: { code: "EUR", decimalPlaces: 2 },
|
|
6
|
-
GBP: { code: "GBP", decimalPlaces: 2 },
|
|
7
|
-
JPY: { code: "JPY", decimalPlaces: 0 },
|
|
8
|
-
CAD: { code: "CAD", decimalPlaces: 2 },
|
|
9
|
-
AUD: { code: "AUD", decimalPlaces: 2 },
|
|
10
|
-
NZD: { code: "NZD", decimalPlaces: 2 },
|
|
11
|
-
CHF: { code: "CHF", decimalPlaces: 2 },
|
|
12
|
-
SEK: { code: "SEK", decimalPlaces: 2 },
|
|
13
|
-
NOK: { code: "NOK", decimalPlaces: 2 },
|
|
14
|
-
DKK: { code: "DKK", decimalPlaces: 2 },
|
|
15
|
-
PLN: { code: "PLN", decimalPlaces: 2 },
|
|
16
|
-
CZK: { code: "CZK", decimalPlaces: 2 },
|
|
17
|
-
HUF: { code: "HUF", decimalPlaces: 2 },
|
|
18
|
-
BRL: { code: "BRL", decimalPlaces: 2 },
|
|
19
|
-
MXN: { code: "MXN", decimalPlaces: 2 },
|
|
20
|
-
ARS: { code: "ARS", decimalPlaces: 2 },
|
|
21
|
-
CLP: { code: "CLP", decimalPlaces: 0 },
|
|
22
|
-
INR: { code: "INR", decimalPlaces: 2 },
|
|
23
|
-
SGD: { code: "SGD", decimalPlaces: 2 },
|
|
24
|
-
HKD: { code: "HKD", decimalPlaces: 2 },
|
|
25
|
-
KRW: { code: "KRW", decimalPlaces: 0 },
|
|
26
|
-
CNY: { code: "CNY", decimalPlaces: 2 },
|
|
27
|
-
ZAR: { code: "ZAR", decimalPlaces: 2 },
|
|
28
|
-
AED: { code: "AED", decimalPlaces: 2 },
|
|
29
|
-
SAR: { code: "SAR", decimalPlaces: 2 },
|
|
30
|
-
BTC: { code: "BTC", decimalPlaces: 8 }
|
|
3
|
+
USD: { code: "USD", decimalPlaces: 2, numericCode: 840, iso4217: true },
|
|
4
|
+
USDH: { code: "USDH", decimalPlaces: 3, iso4217: false, displayCode: "USD" },
|
|
5
|
+
EUR: { code: "EUR", decimalPlaces: 2, numericCode: 978, iso4217: true },
|
|
6
|
+
GBP: { code: "GBP", decimalPlaces: 2, numericCode: 826, iso4217: true },
|
|
7
|
+
JPY: { code: "JPY", decimalPlaces: 0, numericCode: 392, iso4217: true },
|
|
8
|
+
CAD: { code: "CAD", decimalPlaces: 2, numericCode: 124, iso4217: true },
|
|
9
|
+
AUD: { code: "AUD", decimalPlaces: 2, numericCode: 36, iso4217: true },
|
|
10
|
+
NZD: { code: "NZD", decimalPlaces: 2, numericCode: 554, iso4217: true },
|
|
11
|
+
CHF: { code: "CHF", decimalPlaces: 2, numericCode: 756, iso4217: true },
|
|
12
|
+
SEK: { code: "SEK", decimalPlaces: 2, numericCode: 752, iso4217: true },
|
|
13
|
+
NOK: { code: "NOK", decimalPlaces: 2, numericCode: 578, iso4217: true },
|
|
14
|
+
DKK: { code: "DKK", decimalPlaces: 2, numericCode: 208, iso4217: true },
|
|
15
|
+
PLN: { code: "PLN", decimalPlaces: 2, numericCode: 985, iso4217: true },
|
|
16
|
+
CZK: { code: "CZK", decimalPlaces: 2, numericCode: 203, iso4217: true },
|
|
17
|
+
HUF: { code: "HUF", decimalPlaces: 2, numericCode: 348, iso4217: true },
|
|
18
|
+
BRL: { code: "BRL", decimalPlaces: 2, numericCode: 986, iso4217: true },
|
|
19
|
+
MXN: { code: "MXN", decimalPlaces: 2, numericCode: 484, iso4217: true },
|
|
20
|
+
ARS: { code: "ARS", decimalPlaces: 2, numericCode: 32, iso4217: true },
|
|
21
|
+
CLP: { code: "CLP", decimalPlaces: 0, numericCode: 152, iso4217: true },
|
|
22
|
+
INR: { code: "INR", decimalPlaces: 2, numericCode: 356, iso4217: true },
|
|
23
|
+
SGD: { code: "SGD", decimalPlaces: 2, numericCode: 702, iso4217: true },
|
|
24
|
+
HKD: { code: "HKD", decimalPlaces: 2, numericCode: 344, iso4217: true },
|
|
25
|
+
KRW: { code: "KRW", decimalPlaces: 0, numericCode: 410, iso4217: true },
|
|
26
|
+
CNY: { code: "CNY", decimalPlaces: 2, numericCode: 156, iso4217: true },
|
|
27
|
+
ZAR: { code: "ZAR", decimalPlaces: 2, numericCode: 710, iso4217: true },
|
|
28
|
+
AED: { code: "AED", decimalPlaces: 2, numericCode: 784, iso4217: true },
|
|
29
|
+
SAR: { code: "SAR", decimalPlaces: 2, numericCode: 682, iso4217: true },
|
|
30
|
+
BTC: { code: "BTC", decimalPlaces: 8, iso4217: false }
|
|
31
31
|
};
|
|
32
|
-
|
|
32
|
+
var ISO_ALPHA3 = /^[A-Z]{3}$/;
|
|
33
|
+
function defineCurrency(code, decimalPlaces, options = {}) {
|
|
33
34
|
if (!code || typeof code !== "string") {
|
|
34
35
|
throw new Error("Currency code must be a non-empty string");
|
|
35
36
|
}
|
|
36
37
|
if (!Number.isInteger(decimalPlaces) || decimalPlaces < 0) {
|
|
37
38
|
throw new Error("Decimal places must be a non-negative integer");
|
|
38
39
|
}
|
|
39
|
-
|
|
40
|
+
const { numericCode, iso4217, displayCode } = options;
|
|
41
|
+
if (iso4217 === true) {
|
|
42
|
+
if (!ISO_ALPHA3.test(code)) {
|
|
43
|
+
throw new Error(`ISO 4217 currency code must be three uppercase letters: got "${code}"`);
|
|
44
|
+
}
|
|
45
|
+
if (numericCode === undefined || !Number.isInteger(numericCode) || numericCode < 0 || numericCode > 999) {
|
|
46
|
+
throw new Error("ISO 4217 currencies require an integer numericCode in 0..999");
|
|
47
|
+
}
|
|
48
|
+
} else if (numericCode !== undefined) {
|
|
49
|
+
if (!Number.isInteger(numericCode) || numericCode < 0 || numericCode > 999) {
|
|
50
|
+
throw new Error("numericCode must be an integer in 0..999");
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (displayCode !== undefined && !ISO_ALPHA3.test(displayCode)) {
|
|
54
|
+
throw new Error(`displayCode must be three uppercase letters: got "${displayCode}"`);
|
|
55
|
+
}
|
|
56
|
+
const def = { code, decimalPlaces };
|
|
57
|
+
if (numericCode !== undefined)
|
|
58
|
+
def.numericCode = numericCode;
|
|
59
|
+
if (iso4217 !== undefined)
|
|
60
|
+
def.iso4217 = iso4217;
|
|
61
|
+
if (displayCode !== undefined)
|
|
62
|
+
def.displayCode = displayCode;
|
|
63
|
+
return Object.freeze(def);
|
|
40
64
|
}
|
|
41
65
|
// src/money.ts
|
|
42
66
|
var INTERNAL_PRECISION = 20;
|
|
@@ -119,6 +143,27 @@ function assertSafeResult(value) {
|
|
|
119
143
|
throw new Error("Result too large: exceeds safe integer range");
|
|
120
144
|
}
|
|
121
145
|
}
|
|
146
|
+
function minorToDecimalString(minor, decimalPlaces) {
|
|
147
|
+
const negative = minor < 0n;
|
|
148
|
+
const absMinor = negative ? -minor : minor;
|
|
149
|
+
if (decimalPlaces === 0) {
|
|
150
|
+
return `${negative ? "-" : ""}${absMinor}`;
|
|
151
|
+
}
|
|
152
|
+
const scale = 10n ** BigInt(decimalPlaces);
|
|
153
|
+
const whole = absMinor / scale;
|
|
154
|
+
const fraction = (absMinor % scale).toString().padStart(decimalPlaces, "0");
|
|
155
|
+
return `${negative ? "-" : ""}${whole}.${fraction}`;
|
|
156
|
+
}
|
|
157
|
+
function resolveFractionDigits(decimalPlaces, options) {
|
|
158
|
+
const { minimumFractionDigits, maximumFractionDigits } = options;
|
|
159
|
+
return {
|
|
160
|
+
minimumFractionDigits: minimumFractionDigits ?? (maximumFractionDigits === undefined ? decimalPlaces : Math.min(decimalPlaces, maximumFractionDigits)),
|
|
161
|
+
maximumFractionDigits: maximumFractionDigits ?? (minimumFractionDigits === undefined ? decimalPlaces : Math.max(decimalPlaces, minimumFractionDigits))
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function formatExactDecimal(format, value) {
|
|
165
|
+
return format.format(value);
|
|
166
|
+
}
|
|
122
167
|
|
|
123
168
|
class Money {
|
|
124
169
|
#minor;
|
|
@@ -383,6 +428,29 @@ class Money {
|
|
|
383
428
|
currency: this.#currency.code
|
|
384
429
|
};
|
|
385
430
|
}
|
|
431
|
+
format(locales, options = {}) {
|
|
432
|
+
const value = minorToDecimalString(this.#minor, this.#currency.decimalPlaces);
|
|
433
|
+
const dp = this.#currency.decimalPlaces;
|
|
434
|
+
const displayCode = this.#currency.displayCode ?? this.#currency.code;
|
|
435
|
+
const canUseCurrencyStyle = this.#currency.iso4217 === true || this.#currency.displayCode !== undefined;
|
|
436
|
+
const fractionDigits = resolveFractionDigits(dp, options);
|
|
437
|
+
if (canUseCurrencyStyle) {
|
|
438
|
+
try {
|
|
439
|
+
return formatExactDecimal(new Intl.NumberFormat(locales, {
|
|
440
|
+
...options,
|
|
441
|
+
...fractionDigits,
|
|
442
|
+
style: "currency",
|
|
443
|
+
currency: displayCode
|
|
444
|
+
}), value);
|
|
445
|
+
} catch {}
|
|
446
|
+
}
|
|
447
|
+
const number = formatExactDecimal(new Intl.NumberFormat(locales, {
|
|
448
|
+
...options,
|
|
449
|
+
...fractionDigits,
|
|
450
|
+
style: "decimal"
|
|
451
|
+
}), value);
|
|
452
|
+
return `${this.#currency.code} ${number}`;
|
|
453
|
+
}
|
|
386
454
|
}
|
|
387
455
|
export {
|
|
388
456
|
defineCurrency,
|
package/dist/money.d.ts
CHANGED
|
@@ -4,6 +4,10 @@ export interface FromNumberOptions {
|
|
|
4
4
|
rounding?: RoundingMode;
|
|
5
5
|
strict?: boolean;
|
|
6
6
|
}
|
|
7
|
+
type MoneyFormatOptions = Omit<Intl.NumberFormatOptions, "style" | "currency" | "minimumFractionDigits" | "maximumFractionDigits"> & {
|
|
8
|
+
minimumFractionDigits?: number;
|
|
9
|
+
maximumFractionDigits?: number;
|
|
10
|
+
};
|
|
7
11
|
export declare class Money {
|
|
8
12
|
#private;
|
|
9
13
|
private constructor();
|
|
@@ -64,4 +68,17 @@ export declare class Money {
|
|
|
64
68
|
amount: number;
|
|
65
69
|
currency: string;
|
|
66
70
|
};
|
|
71
|
+
/**
|
|
72
|
+
* Format this Money for display using `Intl.NumberFormat`.
|
|
73
|
+
*
|
|
74
|
+
* - For ISO 4217 currencies, uses `style: "currency"` with the standard code
|
|
75
|
+
* (or `displayCode` if provided), respecting the currency's
|
|
76
|
+
* `decimalPlaces` (overrides Intl's locale-default fraction digits).
|
|
77
|
+
* - For non-ISO codes without a `displayCode`, falls back to a decimal
|
|
78
|
+
* format prefixed with the currency code (e.g., `"BTC 0.12345678"`).
|
|
79
|
+
* - If a runtime rejects the currency code (older engines, exotic locales),
|
|
80
|
+
* the same decimal fallback is used.
|
|
81
|
+
*/
|
|
82
|
+
format(locales?: string | string[], options?: MoneyFormatOptions): string;
|
|
67
83
|
}
|
|
84
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storepass/money",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Money primitive library",
|
|
5
5
|
"main": "./dist/index.cjs",
|
|
6
|
-
"module": "./dist/index.
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
7
|
"types": "./dist/index.d.ts",
|
|
8
8
|
"exports": {
|
|
9
9
|
".": {
|
|
10
10
|
"import": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
|
-
"default": "./dist/index.
|
|
12
|
+
"default": "./dist/index.mjs"
|
|
13
13
|
},
|
|
14
14
|
"require": {
|
|
15
15
|
"types": "./dist/index.d.ts",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"scripts": {
|
|
24
24
|
"build": "bun run build:esm && bun run build:cjs && bun run build:types",
|
|
25
|
-
"build:esm": "bun build ./src/index.ts --outfile ./dist/index.
|
|
25
|
+
"build:esm": "bun build ./src/index.ts --outfile ./dist/index.mjs --format esm --target node",
|
|
26
26
|
"build:cjs": "bun build ./src/index.ts --outfile ./dist/index.cjs --format cjs --target node",
|
|
27
27
|
"build:types": "tsc -p tsconfig.build.json",
|
|
28
28
|
"test": "bun test",
|