@arki/money 0.0.0-stage → 0.0.4
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/LICENSE +21 -0
- package/README.md +238 -2
- package/dist/bag.d.ts +48 -0
- package/dist/bag.d.ts.map +1 -0
- package/dist/bag.js +110 -0
- package/dist/bag.js.map +1 -0
- package/dist/currency.d.ts +64 -0
- package/dist/currency.d.ts.map +1 -0
- package/dist/currency.js +148 -0
- package/dist/currency.js.map +1 -0
- package/dist/data/iso4217.d.ts +9 -0
- package/dist/data/iso4217.d.ts.map +1 -0
- package/dist/data/iso4217.js +11 -0
- package/dist/data/iso4217.js.map +1 -0
- package/dist/drizzle/config.d.ts +27 -0
- package/dist/drizzle/config.d.ts.map +1 -0
- package/dist/drizzle/config.js +16 -0
- package/dist/drizzle/config.js.map +1 -0
- package/dist/drizzle/mysql.d.ts +36 -0
- package/dist/drizzle/mysql.d.ts.map +1 -0
- package/dist/drizzle/mysql.js +45 -0
- package/dist/drizzle/mysql.js.map +1 -0
- package/dist/drizzle/pg.d.ts +38 -0
- package/dist/drizzle/pg.d.ts.map +1 -0
- package/dist/drizzle/pg.js +49 -0
- package/dist/drizzle/pg.js.map +1 -0
- package/dist/drizzle/sqlite.d.ts +38 -0
- package/dist/drizzle/sqlite.d.ts.map +1 -0
- package/dist/drizzle/sqlite.js +45 -0
- package/dist/drizzle/sqlite.js.map +1 -0
- package/dist/drizzle.d.ts +56 -0
- package/dist/drizzle.d.ts.map +1 -0
- package/dist/drizzle.js +77 -0
- package/dist/drizzle.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +42 -0
- package/dist/errors.js.map +1 -0
- package/dist/exact.d.ts +20 -0
- package/dist/exact.d.ts.map +1 -0
- package/dist/exact.js +23 -0
- package/dist/exact.js.map +1 -0
- package/dist/exchange.d.ts +84 -0
- package/dist/exchange.d.ts.map +1 -0
- package/dist/exchange.js +215 -0
- package/dist/exchange.js.map +1 -0
- package/dist/format.d.ts +41 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +122 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/columns.d.ts +25 -0
- package/dist/internal/columns.d.ts.map +1 -0
- package/dist/internal/columns.js +47 -0
- package/dist/internal/columns.js.map +1 -0
- package/dist/internal/units.d.ts +66 -0
- package/dist/internal/units.d.ts.map +1 -0
- package/dist/internal/units.js +205 -0
- package/dist/internal/units.js.map +1 -0
- package/dist/money.d.ts +180 -0
- package/dist/money.d.ts.map +1 -0
- package/dist/money.js +577 -0
- package/dist/money.js.map +1 -0
- package/dist/parse.d.ts +53 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +222 -0
- package/dist/parse.js.map +1 -0
- package/dist/provider.d.ts +22 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +56 -0
- package/dist/provider.js.map +1 -0
- package/dist/rational.d.ts +67 -0
- package/dist/rational.d.ts.map +1 -0
- package/dist/rational.js +169 -0
- package/dist/rational.js.map +1 -0
- package/dist/stripe.d.ts +37 -0
- package/dist/stripe.d.ts.map +1 -0
- package/dist/stripe.js +75 -0
- package/dist/stripe.js.map +1 -0
- package/dist/zod.d.ts +54 -0
- package/dist/zod.d.ts.map +1 -0
- package/dist/zod.js +96 -0
- package/dist/zod.js.map +1 -0
- package/package.json +137 -4
- package/src/bag.ts +128 -0
- package/src/currency.ts +174 -0
- package/src/data/iso4217.ts +13 -0
- package/src/drizzle/config.ts +39 -0
- package/src/drizzle/mysql.ts +61 -0
- package/src/drizzle/pg.ts +64 -0
- package/src/drizzle/sqlite.ts +61 -0
- package/src/errors.ts +61 -0
- package/src/exact.ts +29 -0
- package/src/exchange.ts +280 -0
- package/src/format.ts +148 -0
- package/src/index.ts +16 -0
- package/src/internal/columns.ts +74 -0
- package/src/internal/units.ts +223 -0
- package/src/money.ts +692 -0
- package/src/parse.ts +278 -0
- package/src/provider.ts +67 -0
- package/src/rational.ts +214 -0
- package/src/stripe.ts +84 -0
- package/src/zod.ts +123 -0
package/dist/drizzle.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle columns for Postgres that read and write `Money`. Optional: needs
|
|
3
|
+
* `drizzle-orm` 1.x installed.
|
|
4
|
+
*
|
|
5
|
+
* - `moneyMinor(name, { currency })` — a `bigint` column of minor units for one
|
|
6
|
+
* fixed currency ↔ `Money<C>`. Best for sums in SQL.
|
|
7
|
+
* - `moneyDecimal(name, { currency, precision, scale })` — a `numeric` column ↔
|
|
8
|
+
* `Money<C>`. Readable in SQL tools; holds more decimals than the currency.
|
|
9
|
+
* - `moneyJson(name)` — a `jsonb` column of `{ amount, currency }` ↔ `Money`
|
|
10
|
+
* in any currency. For prices that vary by row.
|
|
11
|
+
*
|
|
12
|
+
* Every column declares a `:string`-style codec where Drizzle has one, so each
|
|
13
|
+
* driver (node-postgres, postgres.js, PGlite, Bun SQL) hands back text and no
|
|
14
|
+
* value ever passes through a float. Writing a `Money` in another currency, or
|
|
15
|
+
* with more decimals than the column holds, throws instead of storing a wrong
|
|
16
|
+
* value.
|
|
17
|
+
*/
|
|
18
|
+
import { customType } from 'drizzle-orm/pg-core';
|
|
19
|
+
import { assertSameCurrency, resolveCurrency, toCurrency } from './currency.js';
|
|
20
|
+
import { InvalidArgumentError } from './errors.js';
|
|
21
|
+
import { quantize } from './internal/units.js';
|
|
22
|
+
import { Money as MoneyClass } from './money.js';
|
|
23
|
+
function requireConfig(config, column) {
|
|
24
|
+
if (config === undefined)
|
|
25
|
+
throw new InvalidArgumentError(`${column} needs a config with a currency`);
|
|
26
|
+
return config;
|
|
27
|
+
}
|
|
28
|
+
/** Minor units as text, at the column's scale. Throws if the value needs rounding. */
|
|
29
|
+
export function moneyToMinorText(value, currency, scale) {
|
|
30
|
+
assertSameCurrency(value.currency, currency);
|
|
31
|
+
return String(quantize(value.units, value.scale, scale));
|
|
32
|
+
}
|
|
33
|
+
/** `bigint` column ↔ `Money<C>` in one fixed currency. */
|
|
34
|
+
export function moneyMinor(name, config) {
|
|
35
|
+
const currency = toCurrency(config.currency);
|
|
36
|
+
const scale = config.scale ?? currency.scale;
|
|
37
|
+
return customType({
|
|
38
|
+
dataType: () => 'bigint',
|
|
39
|
+
codec: 'bigint:string',
|
|
40
|
+
toDriver: value => moneyToMinorText(value, currency, scale),
|
|
41
|
+
fromDriver: text => MoneyClass.fromMinor(String(text), currency, scale),
|
|
42
|
+
})(name, requireConfig(config, 'moneyMinor'));
|
|
43
|
+
}
|
|
44
|
+
/** `numeric(precision, scale)` column ↔ `Money<C>` in one fixed currency. */
|
|
45
|
+
export function moneyDecimal(name, config) {
|
|
46
|
+
const currency = toCurrency(config.currency);
|
|
47
|
+
const scale = config.scale ?? currency.scale;
|
|
48
|
+
const precision = config.precision ?? 19;
|
|
49
|
+
return customType({
|
|
50
|
+
dataType: () => `numeric(${precision}, ${scale})`,
|
|
51
|
+
codec: 'numeric',
|
|
52
|
+
toDriver: value => {
|
|
53
|
+
assertSameCurrency(value.currency, currency);
|
|
54
|
+
return value.toScale(scale).toDecimalString();
|
|
55
|
+
},
|
|
56
|
+
fromDriver: text => MoneyClass.of(String(text), currency, { scale }),
|
|
57
|
+
})(name, requireConfig(config, 'moneyDecimal'));
|
|
58
|
+
}
|
|
59
|
+
/** `jsonb` column of `{ amount, currency }` ↔ `Money` in any currency. */
|
|
60
|
+
export function moneyJson(name, config = {}) {
|
|
61
|
+
return customType({
|
|
62
|
+
dataType: () => 'jsonb',
|
|
63
|
+
toDriver: value => JSON.stringify(value.toJSON()),
|
|
64
|
+
fromDriver: stored => {
|
|
65
|
+
const parsed = typeof stored === 'string' ? JSON.parse(stored) : stored;
|
|
66
|
+
if (typeof parsed !== 'object' ||
|
|
67
|
+
parsed === null ||
|
|
68
|
+
!('currency' in parsed) ||
|
|
69
|
+
typeof parsed.currency !== 'string') {
|
|
70
|
+
throw new InvalidArgumentError('A moneyJson column holds { amount, currency }');
|
|
71
|
+
}
|
|
72
|
+
resolveCurrency(parsed.currency, config.currencies);
|
|
73
|
+
return MoneyClass.fromJSON(parsed, config.currencies);
|
|
74
|
+
},
|
|
75
|
+
})(name, config);
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=drizzle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"drizzle.js","sourceRoot":"","sources":["../src/drizzle.ts"],"names":[],"mappings":"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"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every failure this package raises on purpose extends `MathError` from
|
|
3
|
+
* `@arki/math`, so one `instanceof MathError` catches both libraries. The
|
|
4
|
+
* money-only failures also extend `MoneyError`. Wrong runtime types (a number
|
|
5
|
+
* where a `Money` was required, or `money + 1`) throw the built-in `TypeError`.
|
|
6
|
+
* Nothing here returns `NaN` or `null` as a hidden failure channel.
|
|
7
|
+
*/
|
|
8
|
+
import { MathError } from '@arki/math/errors';
|
|
9
|
+
export { DivisionByZeroError, EmptyDataError, InvalidArgumentError, MathError, NumberFormatError, NumericRangeError, ResourceLimitError, RoundingNecessaryError, UnsupportedFeatureError, } from '@arki/math/errors';
|
|
10
|
+
/** Base class of the failures that only money can have. */
|
|
11
|
+
export declare class MoneyError extends MathError {
|
|
12
|
+
}
|
|
13
|
+
/** Two amounts in different currencies were combined or compared. */
|
|
14
|
+
export declare class CurrencyMismatchError extends MoneyError {
|
|
15
|
+
/** Code of the currency on the left-hand side. */
|
|
16
|
+
readonly left: string;
|
|
17
|
+
/** Code of the currency on the right-hand side. */
|
|
18
|
+
readonly right: string;
|
|
19
|
+
constructor(left: string, right: string);
|
|
20
|
+
}
|
|
21
|
+
/** The code is not in the shipped ISO 4217 table and no custom currency was supplied for it. */
|
|
22
|
+
export declare class UnknownCurrencyError extends MoneyError {
|
|
23
|
+
readonly code: string;
|
|
24
|
+
constructor(code: string);
|
|
25
|
+
}
|
|
26
|
+
/** No exchange rate is known for the pair, or a rate is invalid (zero, negative, conflicting). */
|
|
27
|
+
export declare class ExchangeRateError extends MoneyError {
|
|
28
|
+
}
|
|
29
|
+
/** An amount cannot be expressed in a payment provider's units (fraction it does not accept, wrong step, unsupported currency). */
|
|
30
|
+
export declare class ProviderAmountError extends MoneyError {
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,2DAA2D;AAC3D,qBAAa,UAAW,SAAQ,SAAS;CAAG;AAE5C,qEAAqE;AACrE,qBAAa,qBAAsB,SAAQ,UAAU;IACnD,kDAAkD;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAEvB,YAAY,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAQtC;CACF;AAED,gGAAgG;AAChG,qBAAa,oBAAqB,SAAQ,UAAU;IAClD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAEtB,YAAY,IAAI,EAAE,MAAM,EAMvB;CACF;AAED,kGAAkG;AAClG,qBAAa,iBAAkB,SAAQ,UAAU;CAAG;AAEpD,mIAAmI;AACnI,qBAAa,mBAAoB,SAAQ,UAAU;CAAG"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every failure this package raises on purpose extends `MathError` from
|
|
3
|
+
* `@arki/math`, so one `instanceof MathError` catches both libraries. The
|
|
4
|
+
* money-only failures also extend `MoneyError`. Wrong runtime types (a number
|
|
5
|
+
* where a `Money` was required, or `money + 1`) throw the built-in `TypeError`.
|
|
6
|
+
* Nothing here returns `NaN` or `null` as a hidden failure channel.
|
|
7
|
+
*/
|
|
8
|
+
import { MathError } from '@arki/math/errors';
|
|
9
|
+
export { DivisionByZeroError, EmptyDataError, InvalidArgumentError, MathError, NumberFormatError, NumericRangeError, ResourceLimitError, RoundingNecessaryError, UnsupportedFeatureError, } from '@arki/math/errors';
|
|
10
|
+
/** Base class of the failures that only money can have. */
|
|
11
|
+
export class MoneyError extends MathError {
|
|
12
|
+
}
|
|
13
|
+
/** Two amounts in different currencies were combined or compared. */
|
|
14
|
+
export class CurrencyMismatchError extends MoneyError {
|
|
15
|
+
/** Code of the currency on the left-hand side. */
|
|
16
|
+
left;
|
|
17
|
+
/** Code of the currency on the right-hand side. */
|
|
18
|
+
right;
|
|
19
|
+
constructor(left, right) {
|
|
20
|
+
super(left === right
|
|
21
|
+
? `Cannot combine two different definitions of ${left}; they disagree on the number of decimals`
|
|
22
|
+
: `Cannot combine ${left} with ${right}`);
|
|
23
|
+
this.left = left;
|
|
24
|
+
this.right = right;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/** The code is not in the shipped ISO 4217 table and no custom currency was supplied for it. */
|
|
28
|
+
export class UnknownCurrencyError extends MoneyError {
|
|
29
|
+
code;
|
|
30
|
+
constructor(code) {
|
|
31
|
+
super(`Unknown currency ${JSON.stringify(code)}. ISO 4217 codes are three upper-case letters; ` +
|
|
32
|
+
'for anything else create one with Currency.custom()');
|
|
33
|
+
this.code = code;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** No exchange rate is known for the pair, or a rate is invalid (zero, negative, conflicting). */
|
|
37
|
+
export class ExchangeRateError extends MoneyError {
|
|
38
|
+
}
|
|
39
|
+
/** An amount cannot be expressed in a payment provider's units (fraction it does not accept, wrong step, unsupported currency). */
|
|
40
|
+
export class ProviderAmountError extends MoneyError {
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,oBAAoB,EACpB,SAAS,EACT,iBAAiB,EACjB,iBAAiB,EACjB,kBAAkB,EAClB,sBAAsB,EACtB,uBAAuB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,2DAA2D;AAC3D,MAAM,OAAO,UAAW,SAAQ,SAAS;CAAG;AAE5C,qEAAqE;AACrE,MAAM,OAAO,qBAAsB,SAAQ,UAAU;IACnD,kDAAkD;IACzC,IAAI,CAAS;IACtB,mDAAmD;IAC1C,KAAK,CAAS;IAEvB,YAAY,IAAY,EAAE,KAAa;QACrC,KAAK,CACH,IAAI,KAAK,KAAK;YACZ,CAAC,CAAC,+CAA+C,IAAI,2CAA2C;YAChG,CAAC,CAAC,kBAAkB,IAAI,SAAS,KAAK,EAAE,CAC3C,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,gGAAgG;AAChG,MAAM,OAAO,oBAAqB,SAAQ,UAAU;IACzC,IAAI,CAAS;IAEtB,YAAY,IAAY;QACtB,KAAK,CACH,oBAAoB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,iDAAiD;YACvF,qDAAqD,CACxD,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF;AAED,kGAAkG;AAClG,MAAM,OAAO,iBAAkB,SAAQ,UAAU;CAAG;AAEpD,mIAAmI;AACnI,MAAM,OAAO,mBAAoB,SAAQ,UAAU;CAAG"}
|
package/dist/exact.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges to `@arki/math`'s exact types, for multi-step math that should
|
|
3
|
+
* round only once at the end:
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const share = toRational(price).times(ratio(1, 3)).plus(ratio(1, 7));
|
|
7
|
+
* Money.of(share, 'EUR', { rounding: 'halfEven' }); // Money.of reads Decimal and Rational directly
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Kept in its own entry point so the core never loads the `Decimal` and
|
|
11
|
+
* `Rational` classes.
|
|
12
|
+
*/
|
|
13
|
+
import type { Decimal } from '@arki/math/decimal';
|
|
14
|
+
import type { Rational } from '@arki/math/rational';
|
|
15
|
+
import type { Money } from './money.js';
|
|
16
|
+
/** `19.99 EUR` → `Decimal('19.99')`, keeping the scale. */
|
|
17
|
+
export declare function toDecimal(money: Money): Decimal;
|
|
18
|
+
/** `19.99 EUR` → `1999/100`, reduced. */
|
|
19
|
+
export declare function toRational(money: Money): Rational;
|
|
20
|
+
//# sourceMappingURL=exact.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact.d.ts","sourceRoot":"","sources":["../src/exact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAIpD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,2DAA2D;AAC3D,wBAAgB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAE/C;AAED,yCAAyC;AACzC,wBAAgB,UAAU,CAAC,KAAK,EAAE,KAAK,GAAG,QAAQ,CAEjD"}
|
package/dist/exact.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bridges to `@arki/math`'s exact types, for multi-step math that should
|
|
3
|
+
* round only once at the end:
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* const share = toRational(price).times(ratio(1, 3)).plus(ratio(1, 7));
|
|
7
|
+
* Money.of(share, 'EUR', { rounding: 'halfEven' }); // Money.of reads Decimal and Rational directly
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Kept in its own entry point so the core never loads the `Decimal` and
|
|
11
|
+
* `Rational` classes.
|
|
12
|
+
*/
|
|
13
|
+
import { decimal } from '@arki/math/decimal';
|
|
14
|
+
import { ratio } from '@arki/math/rational';
|
|
15
|
+
/** `19.99 EUR` → `Decimal('19.99')`, keeping the scale. */
|
|
16
|
+
export function toDecimal(money) {
|
|
17
|
+
return decimal(money.toDecimalString());
|
|
18
|
+
}
|
|
19
|
+
/** `19.99 EUR` → `1999/100`, reduced. */
|
|
20
|
+
export function toRational(money) {
|
|
21
|
+
return ratio(BigInt(money.units), 10n ** BigInt(money.scale));
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=exact.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exact.js","sourceRoot":"","sources":["../src/exact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAIH,OAAO,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAI5C,2DAA2D;AAC3D,MAAM,UAAU,SAAS,CAAC,KAAY;IACpC,OAAO,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,CAAC,CAAC;AAC1C,CAAC;AAED,yCAAyC;AACzC,MAAM,UAAU,UAAU,CAAC,KAAY;IACrC,OAAO,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,GAAG,IAAI,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency conversion with exact rates. A rate is a fraction, never a float:
|
|
3
|
+
* `1 EUR = 1.0832 USD` is stored as `677/625`. Inverse and cross
|
|
4
|
+
* rates stay exact, and a conversion rounds once, at the end.
|
|
5
|
+
*
|
|
6
|
+
* There is no network code. Fetch rates however you like (ECB, a bank, your
|
|
7
|
+
* own table), then build rates from them here.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const converter = createConverter(ratesFrom({ base: 'EUR', rates: { USD: '1.0832', GBP: '0.8571' } }), {
|
|
11
|
+
* inverse: true,
|
|
12
|
+
* via: 'EUR',
|
|
13
|
+
* });
|
|
14
|
+
* converter.convert(money(100, 'USD'), 'GBP', { rounding: 'halfEven' }); // USD → EUR → GBP, rounded once
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { CurrencyLike } from './currency.js';
|
|
18
|
+
import type { Units } from './internal/units.js';
|
|
19
|
+
import type { Amount, MoneyOptions } from './money.js';
|
|
20
|
+
import { Currency } from './currency.js';
|
|
21
|
+
import { Money } from './money.js';
|
|
22
|
+
/** An exact, directed rate: one unit of `base` buys `numerator / denominator` units of `quote`. */
|
|
23
|
+
export declare class ExchangeRate<B extends string = string, Q extends string = string> {
|
|
24
|
+
readonly base: Currency<B>;
|
|
25
|
+
readonly quote: Currency<Q>;
|
|
26
|
+
/** Positive, in lowest terms with `denominator`. */
|
|
27
|
+
readonly numerator: Units;
|
|
28
|
+
/** Positive. */
|
|
29
|
+
readonly denominator: Units;
|
|
30
|
+
private constructor();
|
|
31
|
+
/** The reverse rate, exact: `EUR/USD 1.0832` → `USD/EUR 625/677`. */
|
|
32
|
+
inverse(): ExchangeRate<Q, B>;
|
|
33
|
+
/** Chains two rates, exact: `EUR→USD` then `USD→JPY` gives `EUR→JPY`. */
|
|
34
|
+
chain<R extends string>(next: ExchangeRate<Q, R>): ExchangeRate<B, R>;
|
|
35
|
+
/** `true` for the same pair and the same exact value. */
|
|
36
|
+
eq(other: ExchangeRate): boolean;
|
|
37
|
+
/** `'EUR/USD 1.0832'`, or `'USD/EUR 625/677'` when the rate has no finite decimal form. */
|
|
38
|
+
toString(): string;
|
|
39
|
+
}
|
|
40
|
+
/** `exchangeRate('EUR', 'USD', '1.0832')`: one euro buys 1.0832 dollars. The value must be positive. */
|
|
41
|
+
export declare function exchangeRate<const B extends string, const Q extends string>(base: CurrencyLike<B>, quote: CurrencyLike<Q>, value: Amount): ExchangeRate<B, Q>;
|
|
42
|
+
/** A table of rates from one base currency, as central banks publish them. */
|
|
43
|
+
export type RateTable = {
|
|
44
|
+
base: CurrencyLike;
|
|
45
|
+
/** Quote code → units of that currency one unit of `base` buys. */
|
|
46
|
+
rates: Readonly<Record<string, Amount>>;
|
|
47
|
+
/** Custom currencies used as quote codes. */
|
|
48
|
+
currencies?: readonly Currency[];
|
|
49
|
+
};
|
|
50
|
+
/** `ratesFrom({ base: 'EUR', rates: { USD: '1.0832' } })` → `[EUR/USD 1.0832]`. */
|
|
51
|
+
export declare function ratesFrom(table: RateTable): ExchangeRate[];
|
|
52
|
+
/**
|
|
53
|
+
* Converts with one rate. The rate's base must be the amount's currency. The
|
|
54
|
+
* exact result is rounded once to the target scale (the quote currency's,
|
|
55
|
+
* or `options.scale`); an inexact result needs `options.rounding`.
|
|
56
|
+
*/
|
|
57
|
+
export declare function convert<B extends string, Q extends string>(amount: Money<B>, rate: ExchangeRate<NoInfer<B>, Q>, options?: MoneyOptions): Money<Q>;
|
|
58
|
+
export type ConverterOptions = {
|
|
59
|
+
/** Also use `B→A` reversed when only `A→B` is known. Off by default: a bid rate reversed is not an ask rate. */
|
|
60
|
+
inverse?: boolean;
|
|
61
|
+
/** Route through this currency when no direct rate exists: `USD→GBP` as `USD→EUR→GBP`. */
|
|
62
|
+
via?: CurrencyLike;
|
|
63
|
+
};
|
|
64
|
+
/** A fixed set of rates with lookups. Build it with `createConverter`. Immutable. */
|
|
65
|
+
export declare class Converter {
|
|
66
|
+
private readonly rates;
|
|
67
|
+
private readonly inverse;
|
|
68
|
+
private readonly via;
|
|
69
|
+
/** Resolved lookups, including misses. Rates never change, so the cache never goes stale. */
|
|
70
|
+
private readonly resolved;
|
|
71
|
+
constructor(rates: Iterable<ExchangeRate>, options?: ConverterOptions);
|
|
72
|
+
/** The exact rate used for `from → to`. Throws `ExchangeRateError` when none can be found. */
|
|
73
|
+
rate<const F extends string, const T extends string>(from: CurrencyLike<F>, to: CurrencyLike<T>): ExchangeRate<F, T>;
|
|
74
|
+
/** `true` when `rate(from, to)` would succeed. */
|
|
75
|
+
has(from: CurrencyLike, to: CurrencyLike): boolean;
|
|
76
|
+
/** Converts and rounds once. Same-currency conversion needs no rate and only changes the scale. */
|
|
77
|
+
convert<const T extends string>(amount: Money, to: CurrencyLike<T>, options?: MoneyOptions): Money<T>;
|
|
78
|
+
private resolve;
|
|
79
|
+
private lookup;
|
|
80
|
+
private lookupVia;
|
|
81
|
+
}
|
|
82
|
+
/** `createConverter(rates, { inverse: true, via: 'EUR' })`. */
|
|
83
|
+
export declare function createConverter(rates: Iterable<ExchangeRate>, options?: ConverterOptions): Converter;
|
|
84
|
+
//# sourceMappingURL=exchange.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange.d.ts","sourceRoot":"","sources":["../src/exchange.ts"],"names":[],"mappings":"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"}
|
package/dist/exchange.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Currency conversion with exact rates. A rate is a fraction, never a float:
|
|
3
|
+
* `1 EUR = 1.0832 USD` is stored as `677/625`. Inverse and cross
|
|
4
|
+
* rates stay exact, and a conversion rounds once, at the end.
|
|
5
|
+
*
|
|
6
|
+
* There is no network code. Fetch rates however you like (ECB, a bank, your
|
|
7
|
+
* own table), then build rates from them here.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* const converter = createConverter(ratesFrom({ base: 'EUR', rates: { USD: '1.0832', GBP: '0.8571' } }), {
|
|
11
|
+
* inverse: true,
|
|
12
|
+
* via: 'EUR',
|
|
13
|
+
* });
|
|
14
|
+
* converter.convert(money(100, 'USD'), 'GBP', { rounding: 'halfEven' }); // USD → EUR → GBP, rounded once
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { assertSameCurrency, Currency, toCurrency } from './currency.js';
|
|
18
|
+
import { ExchangeRateError } from './errors.js';
|
|
19
|
+
import { divExact, gcdUnits, mulDiv, mulUnits, pow10, quantize } from './internal/units.js';
|
|
20
|
+
import { Money, toFraction } from './money.js';
|
|
21
|
+
const BRAND = Symbol('ExchangeRate');
|
|
22
|
+
/** An exact, directed rate: one unit of `base` buys `numerator / denominator` units of `quote`. */
|
|
23
|
+
export class ExchangeRate {
|
|
24
|
+
base;
|
|
25
|
+
quote;
|
|
26
|
+
/** Positive, in lowest terms with `denominator`. */
|
|
27
|
+
numerator;
|
|
28
|
+
/** Positive. */
|
|
29
|
+
denominator;
|
|
30
|
+
constructor(base, quote, numerator, denominator, brand) {
|
|
31
|
+
if (brand !== BRAND)
|
|
32
|
+
throw new TypeError('Use exchangeRate(base, quote, value) instead of new ExchangeRate()');
|
|
33
|
+
this.base = base;
|
|
34
|
+
this.quote = quote;
|
|
35
|
+
const divisor = gcdUnits(numerator, denominator);
|
|
36
|
+
this.numerator = divExact(numerator, divisor);
|
|
37
|
+
this.denominator = divExact(denominator, divisor);
|
|
38
|
+
Object.freeze(this);
|
|
39
|
+
}
|
|
40
|
+
/** @internal */
|
|
41
|
+
static create(base, quote, numerator, denominator) {
|
|
42
|
+
return new ExchangeRate(base, quote, numerator, denominator, BRAND);
|
|
43
|
+
}
|
|
44
|
+
/** The reverse rate, exact: `EUR/USD 1.0832` → `USD/EUR 625/677`. */
|
|
45
|
+
inverse() {
|
|
46
|
+
return new ExchangeRate(this.quote, this.base, this.denominator, this.numerator, BRAND);
|
|
47
|
+
}
|
|
48
|
+
/** Chains two rates, exact: `EUR→USD` then `USD→JPY` gives `EUR→JPY`. */
|
|
49
|
+
chain(next) {
|
|
50
|
+
assertSameCurrency(this.quote, next.base);
|
|
51
|
+
return new ExchangeRate(this.base, next.quote, mulUnits(this.numerator, next.numerator), mulUnits(this.denominator, next.denominator), BRAND);
|
|
52
|
+
}
|
|
53
|
+
/** `true` for the same pair and the same exact value. */
|
|
54
|
+
eq(other) {
|
|
55
|
+
return (this.base.eq(other.base) &&
|
|
56
|
+
this.quote.eq(other.quote) &&
|
|
57
|
+
this.numerator === other.numerator &&
|
|
58
|
+
this.denominator === other.denominator);
|
|
59
|
+
}
|
|
60
|
+
/** `'EUR/USD 1.0832'`, or `'USD/EUR 625/677'` when the rate has no finite decimal form. */
|
|
61
|
+
toString() {
|
|
62
|
+
return `${this.base.code}/${this.quote.code} ${rateText(this.numerator, this.denominator)}`;
|
|
63
|
+
}
|
|
64
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
65
|
+
return `ExchangeRate(${this.toString()})`;
|
|
66
|
+
}
|
|
67
|
+
[Symbol.for('Deno.customInspect')]() {
|
|
68
|
+
return `ExchangeRate(${this.toString()})`;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function rateText(numerator, denominator) {
|
|
72
|
+
// A fraction has a finite decimal form when its denominator has no prime factors other than 2 and 5.
|
|
73
|
+
let rest = BigInt(denominator);
|
|
74
|
+
let twos = 0;
|
|
75
|
+
let fives = 0;
|
|
76
|
+
while (rest % 2n === 0n) {
|
|
77
|
+
rest /= 2n;
|
|
78
|
+
twos++;
|
|
79
|
+
}
|
|
80
|
+
while (rest % 5n === 0n) {
|
|
81
|
+
rest /= 5n;
|
|
82
|
+
fives++;
|
|
83
|
+
}
|
|
84
|
+
if (rest !== 1n)
|
|
85
|
+
return `${String(numerator)}/${String(denominator)}`;
|
|
86
|
+
const scale = Math.max(twos, fives);
|
|
87
|
+
const digits = String(mulDiv(numerator, pow10(scale), denominator)).padStart(scale + 1, '0');
|
|
88
|
+
return scale === 0 ? digits : `${digits.slice(0, -scale)}.${digits.slice(-scale)}`;
|
|
89
|
+
}
|
|
90
|
+
/** `exchangeRate('EUR', 'USD', '1.0832')`: one euro buys 1.0832 dollars. The value must be positive. */
|
|
91
|
+
export function exchangeRate(base, quote, value) {
|
|
92
|
+
const from = toCurrency(base);
|
|
93
|
+
const to = toCurrency(quote);
|
|
94
|
+
const [numerator, denominator] = toFraction(value);
|
|
95
|
+
if (!(numerator > 0))
|
|
96
|
+
throw new ExchangeRateError(`An exchange rate must be positive (${from.code}/${to.code})`);
|
|
97
|
+
if (from.eq(to) && numerator !== denominator) {
|
|
98
|
+
throw new ExchangeRateError(`A rate from ${from.code} to itself must be 1`);
|
|
99
|
+
}
|
|
100
|
+
return ExchangeRate.create(from, to, numerator, denominator);
|
|
101
|
+
}
|
|
102
|
+
/** `ratesFrom({ base: 'EUR', rates: { USD: '1.0832' } })` → `[EUR/USD 1.0832]`. */
|
|
103
|
+
export function ratesFrom(table) {
|
|
104
|
+
const base = toCurrency(table.base);
|
|
105
|
+
return Object.entries(table.rates).map(([code, value]) => {
|
|
106
|
+
const quote = table.currencies?.find(currency => currency.code === code) ?? Currency.of(code);
|
|
107
|
+
return exchangeRate(base, quote, value);
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Converts with one rate. The rate's base must be the amount's currency. The
|
|
112
|
+
* exact result is rounded once to the target scale (the quote currency's,
|
|
113
|
+
* or `options.scale`); an inexact result needs `options.rounding`.
|
|
114
|
+
*/
|
|
115
|
+
export function convert(amount, rate, options) {
|
|
116
|
+
assertSameCurrency(amount.currency, rate.base);
|
|
117
|
+
const scale = options?.scale ?? rate.quote.scale;
|
|
118
|
+
return Money.fromMinor(convertUnits(amount, rate, scale, options), rate.quote, scale);
|
|
119
|
+
}
|
|
120
|
+
/** The one rounding of a conversion: `units × rate × 10^(target − source scale)`. */
|
|
121
|
+
function convertUnits(amount, rate, scale, options) {
|
|
122
|
+
return mulDiv(amount.units, mulUnits(rate.numerator, pow10(scale)), mulUnits(rate.denominator, pow10(amount.scale)), options?.rounding);
|
|
123
|
+
}
|
|
124
|
+
/** A fixed set of rates with lookups. Build it with `createConverter`. Immutable. */
|
|
125
|
+
export class Converter {
|
|
126
|
+
rates;
|
|
127
|
+
inverse;
|
|
128
|
+
via;
|
|
129
|
+
/** Resolved lookups, including misses. Rates never change, so the cache never goes stale. */
|
|
130
|
+
resolved = new Map();
|
|
131
|
+
constructor(rates, options = {}) {
|
|
132
|
+
const map = new Map();
|
|
133
|
+
for (const rate of rates) {
|
|
134
|
+
if (!(rate instanceof ExchangeRate))
|
|
135
|
+
throw new TypeError('Expected ExchangeRate values');
|
|
136
|
+
const key = pairKey(rate.base, rate.quote);
|
|
137
|
+
const existing = map.get(key);
|
|
138
|
+
if (existing !== undefined && !existing.eq(rate)) {
|
|
139
|
+
throw new ExchangeRateError(`Two different rates for ${rate.base.code}/${rate.quote.code}`);
|
|
140
|
+
}
|
|
141
|
+
map.set(key, rate);
|
|
142
|
+
}
|
|
143
|
+
this.rates = map;
|
|
144
|
+
this.inverse = options.inverse === true;
|
|
145
|
+
this.via = options.via === undefined ? undefined : toCurrency(options.via);
|
|
146
|
+
}
|
|
147
|
+
/** The exact rate used for `from → to`. Throws `ExchangeRateError` when none can be found. */
|
|
148
|
+
rate(from, to) {
|
|
149
|
+
const a = toCurrency(from);
|
|
150
|
+
const b = toCurrency(to);
|
|
151
|
+
const found = this.resolve(a, b);
|
|
152
|
+
return ExchangeRate.create(a, b, found.numerator, found.denominator);
|
|
153
|
+
}
|
|
154
|
+
/** `true` when `rate(from, to)` would succeed. */
|
|
155
|
+
has(from, to) {
|
|
156
|
+
const a = toCurrency(from);
|
|
157
|
+
const b = toCurrency(to);
|
|
158
|
+
try {
|
|
159
|
+
this.resolve(a, b);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
catch (error) {
|
|
163
|
+
if (error instanceof ExchangeRateError)
|
|
164
|
+
return false;
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/** Converts and rounds once. Same-currency conversion needs no rate and only changes the scale. */
|
|
169
|
+
convert(amount, to, options) {
|
|
170
|
+
const target = toCurrency(to);
|
|
171
|
+
if (amount.currency.eq(target)) {
|
|
172
|
+
const scale = options?.scale ?? target.scale;
|
|
173
|
+
return Money.fromMinor(quantize(amount.units, amount.scale, scale, options?.rounding), target, scale);
|
|
174
|
+
}
|
|
175
|
+
const scale = options?.scale ?? target.scale;
|
|
176
|
+
return Money.fromMinor(convertUnits(amount, this.resolve(amount.currency, target), scale, options), target, scale);
|
|
177
|
+
}
|
|
178
|
+
resolve(from, to) {
|
|
179
|
+
const key = pairKey(from, to);
|
|
180
|
+
let found = this.resolved.get(key);
|
|
181
|
+
if (found === undefined) {
|
|
182
|
+
found = this.lookup(from, to) ?? this.lookupVia(from, to) ?? null;
|
|
183
|
+
this.resolved.set(key, found);
|
|
184
|
+
}
|
|
185
|
+
if (found === null)
|
|
186
|
+
throw new ExchangeRateError(`No exchange rate from ${from.code} to ${to.code}`);
|
|
187
|
+
return found;
|
|
188
|
+
}
|
|
189
|
+
lookup(from, to) {
|
|
190
|
+
if (from.eq(to))
|
|
191
|
+
return ExchangeRate.create(from, to, 1, 1);
|
|
192
|
+
const direct = this.rates.get(pairKey(from, to));
|
|
193
|
+
if (direct !== undefined)
|
|
194
|
+
return direct;
|
|
195
|
+
if (this.inverse)
|
|
196
|
+
return this.rates.get(pairKey(to, from))?.inverse();
|
|
197
|
+
return undefined;
|
|
198
|
+
}
|
|
199
|
+
lookupVia(from, to) {
|
|
200
|
+
const via = this.via;
|
|
201
|
+
if (via === undefined || via.eq(from) || via.eq(to))
|
|
202
|
+
return undefined;
|
|
203
|
+
const first = this.lookup(from, via);
|
|
204
|
+
const second = this.lookup(via, to);
|
|
205
|
+
return first === undefined || second === undefined ? undefined : first.chain(second);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function pairKey(from, to) {
|
|
209
|
+
return `${from.code}>${to.code}`;
|
|
210
|
+
}
|
|
211
|
+
/** `createConverter(rates, { inverse: true, via: 'EUR' })`. */
|
|
212
|
+
export function createConverter(rates, options) {
|
|
213
|
+
return new Converter(rates, options);
|
|
214
|
+
}
|
|
215
|
+
//# sourceMappingURL=exchange.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"exchange.js","sourceRoot":"","sources":["../src/exchange.ts"],"names":[],"mappings":"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"}
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Localized display through `Intl.NumberFormat`. The amount reaches `Intl` as
|
|
3
|
+
* exact decimal text, never as a float, so digits past 2^53 survive on runtimes
|
|
4
|
+
* that support string input (ES2023). Where a runtime lacks a feature you asked
|
|
5
|
+
* for (React Native's Hermes on iOS lacks some), the call throws
|
|
6
|
+
* `UnsupportedFeatureError` instead of quietly printing something else.
|
|
7
|
+
*
|
|
8
|
+
* Rounding for display is done exactly by this package, not by `Intl`, so it
|
|
9
|
+
* matches arithmetic rounding everywhere.
|
|
10
|
+
*/
|
|
11
|
+
import type { RoundingMode } from '@arki/math/rounding';
|
|
12
|
+
import type { Money } from './money.js';
|
|
13
|
+
export type FormatOptions = {
|
|
14
|
+
/** BCP 47 locale. Defaults to `'en-US'`; there is no global locale. */
|
|
15
|
+
locale?: string;
|
|
16
|
+
/** How to show the currency. Defaults to `'symbol'` for ISO currencies and `'code'` for custom ones. */
|
|
17
|
+
display?: 'symbol' | 'narrowSymbol' | 'code' | 'name';
|
|
18
|
+
/** `($5.00)` instead of `-$5.00` where the locale has an accounting style. */
|
|
19
|
+
accounting?: boolean;
|
|
20
|
+
signDisplay?: 'auto' | 'always' | 'exceptZero' | 'negative' | 'never';
|
|
21
|
+
/** Defaults to `true`. */
|
|
22
|
+
useGrouping?: boolean;
|
|
23
|
+
/** Decimals to show. Defaults to the amount's scale, so nothing is hidden. */
|
|
24
|
+
fractionDigits?: number;
|
|
25
|
+
/** Needed when `fractionDigits` drops non-zero digits. */
|
|
26
|
+
rounding?: RoundingMode;
|
|
27
|
+
};
|
|
28
|
+
export type MoneyFormatter = {
|
|
29
|
+
format(money: Money): string;
|
|
30
|
+
formatToParts(money: Money): Intl.NumberFormatPart[];
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* `formatMoney(money('1234.5', 'EUR'), { locale: 'de-DE' })` → `'1.234,50 €'`.
|
|
34
|
+
* Custom currencies print the number and the code: `'0.00012000 BTC'`.
|
|
35
|
+
*/
|
|
36
|
+
export declare function formatMoney(money: Money, options?: FormatOptions): string;
|
|
37
|
+
/** The parts `Intl` produces (currency, integer, group, decimal, fraction, ...), for custom rendering. */
|
|
38
|
+
export declare function formatMoneyToParts(money: Money, options?: FormatOptions): Intl.NumberFormatPart[];
|
|
39
|
+
/** Binds options once for loops: `const eur = createMoneyFormatter({ locale: 'de-DE' }); eur.format(price)`. */
|
|
40
|
+
export declare function createMoneyFormatter(options?: FormatOptions): MoneyFormatter;
|
|
41
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGxD,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,MAAM,MAAM,aAAa,GAAG;IAC1B,uEAAuE;IACvE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wGAAwG;IACxG,OAAO,CAAC,EAAE,QAAQ,GAAG,cAAc,GAAG,MAAM,GAAG,MAAM,CAAC;IACtD,8EAA8E;IAC9E,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,GAAG,QAAQ,GAAG,YAAY,GAAG,UAAU,GAAG,OAAO,CAAC;IACtE,0BAA0B;IAC1B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB,CAAC;AAEF,MAAM,MAAM,cAAc,GAAG;IAC3B,MAAM,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAAC;IAC7B,aAAa,CAAC,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC,gBAAgB,EAAE,CAAC;CACtD,CAAC;AAmFF;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,aAAkB,GAAG,MAAM,CAI7E;AAED,0GAA0G;AAC1G,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,KAAK,EAAE,OAAO,GAAE,aAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,CASrG;AAED,gHAAgH;AAChH,wBAAgB,oBAAoB,CAAC,OAAO,GAAE,aAAkB,GAAG,cAAc,CAMhF"}
|