@onrail-xyz/amount 1.0.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/LICENSE +201 -0
- package/README.md +617 -0
- package/dist/aggregating.d.ts +31 -0
- package/dist/aggregating.d.ts.map +1 -0
- package/dist/aggregating.js +31 -0
- package/dist/allocating.d.ts +9 -0
- package/dist/allocating.d.ts.map +1 -0
- package/dist/allocating.js +31 -0
- package/dist/amount.d.ts +68 -0
- package/dist/amount.d.ts.map +1 -0
- package/dist/amount.js +184 -0
- package/dist/format.d.ts +15 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +226 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/jsonCodecs.d.ts +9 -0
- package/dist/jsonCodecs.d.ts.map +1 -0
- package/dist/jsonCodecs.js +46 -0
- package/dist/kind.d.ts +126 -0
- package/dist/kind.d.ts.map +1 -0
- package/dist/kind.js +118 -0
- package/dist/rate.d.ts +58 -0
- package/dist/rate.d.ts.map +1 -0
- package/dist/rate.js +178 -0
- package/dist/rational.d.ts +49 -0
- package/dist/rational.d.ts.map +1 -0
- package/dist/rational.js +378 -0
- package/dist/segmenting.d.ts +15 -0
- package/dist/segmenting.d.ts.map +1 -0
- package/dist/segmenting.js +97 -0
- package/dist/unitSpecs.d.ts +47 -0
- package/dist/unitSpecs.d.ts.map +1 -0
- package/dist/unitSpecs.js +8 -0
- package/package.json +56 -0
- package/src/aggregating.ts +160 -0
- package/src/allocating.ts +70 -0
- package/src/amount.ts +291 -0
- package/src/format.ts +330 -0
- package/src/index.ts +8 -0
- package/src/jsonCodecs.ts +63 -0
- package/src/kind.ts +442 -0
- package/src/rate.ts +316 -0
- package/src/rational.ts +471 -0
- package/src/segmenting.ts +154 -0
- package/src/unitSpecs.ts +37 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aggregating.d.ts","sourceRoot":"","sources":["../src/aggregating.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Rational } from "./rational.js";
|
|
2
|
+
import { isAmount } from "./amount.js";
|
|
3
|
+
import { isRate } from "./rate.js";
|
|
4
|
+
export function min(first, ...rest) {
|
|
5
|
+
if (isAmount(first) || isRate(first))
|
|
6
|
+
return rest.reduce((acc, value) => acc.le(value) ? acc : value, first);
|
|
7
|
+
return rest.reduce((acc, value) => acc.le(value) ? acc : Rational.from(value), Rational.from(first));
|
|
8
|
+
}
|
|
9
|
+
export function max(first, ...rest) {
|
|
10
|
+
if (isAmount(first) || isRate(first))
|
|
11
|
+
return rest.reduce((acc, value) => acc.ge(value) ? acc : value, first);
|
|
12
|
+
return rest.reduce((acc, value) => acc.ge(value) ? acc : Rational.from(value), Rational.from(first));
|
|
13
|
+
}
|
|
14
|
+
export function sum(first, ...rest) {
|
|
15
|
+
if (typeof first === "number" || typeof first === "bigint")
|
|
16
|
+
first = Rational.from(first);
|
|
17
|
+
return rest.reduce((acc, value) => acc.add(value), first);
|
|
18
|
+
}
|
|
19
|
+
export function compare(a, b) {
|
|
20
|
+
if (typeof a === "number" || typeof a === "bigint")
|
|
21
|
+
a = Rational.from(a);
|
|
22
|
+
if (a instanceof Rational)
|
|
23
|
+
return a.cmp(b);
|
|
24
|
+
//no subtraction: a sort calls this n·log n times, and an amount's or rate's own gt/lt check
|
|
25
|
+
// the kinds
|
|
26
|
+
const ordered = a;
|
|
27
|
+
return ordered.gt(b) ? 1 : ordered.lt(b) ? -1 : 0;
|
|
28
|
+
}
|
|
29
|
+
export function clamp(x, lo, hi) {
|
|
30
|
+
return min(max(x, lo), hi);
|
|
31
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { OfLength, RoArray, RoNeTuple } from "@onrail-xyz/utils";
|
|
2
|
+
import type { Rationalish } from "./rational.js";
|
|
3
|
+
import type { Kind, KindWithAtomic, SymbolsOf } from "./kind.js";
|
|
4
|
+
import type { Amount } from "./amount.js";
|
|
5
|
+
type Allocation<K extends Kind, P extends number | RoArray<Rationalish>> = P extends RoArray<Rationalish> ? OfLength<Amount<K>, P["length"]> : OfLength<Amount<K>, P & number>;
|
|
6
|
+
export declare function allocate<K extends KindWithAtomic, const P extends number | RoNeTuple<Rationalish>>(amount: Amount<K>, parts: P): Allocation<K, P>;
|
|
7
|
+
export declare function allocate<K extends Kind, const P extends number | RoNeTuple<Rationalish>>(amount: Amount<K>, parts: P, unitSymbol: SymbolsOf<K>): Allocation<K, P>;
|
|
8
|
+
export {};
|
|
9
|
+
//# sourceMappingURL=allocating.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"allocating.d.ts","sourceRoot":"","sources":["../src/allocating.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAEjD,OAAO,KAAK,EAAE,IAAI,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAI1C,KAAK,UAAU,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,SAAS,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,IACrE,CAAC,SAAS,OAAO,CAAC,WAAW,CAAC,GAC5B,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,GAChC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC;AAOpC,wBAAgB,QAAQ,CAChB,CAAC,SAAS,cAAc,EAC9B,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,EAC/C,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;AACjD,wBAAgB,QAAQ,CAChB,CAAC,SAAS,IAAI,EACpB,KAAK,CAAC,CAAC,SAAS,MAAM,GAAG,SAAS,CAAC,WAAW,CAAC,EAC/C,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { isArray } from "@onrail-xyz/utils";
|
|
2
|
+
import { Rational } from "./rational.js";
|
|
3
|
+
export function allocate(amount, parts, unitSymbol = "atomic") {
|
|
4
|
+
if (!amount.eq(amount.floorTo(unitSymbol)))
|
|
5
|
+
throw new Error(`Amount is not a whole number of ${unitSymbol} units - quantize it first ` +
|
|
6
|
+
`(floorTo/roundTo/ceilTo)`);
|
|
7
|
+
if (isArray(parts) ? parts.length === 0 : !(Number.isSafeInteger(parts) && parts > 0))
|
|
8
|
+
throw new Error("parts must be a nonempty weights list or a positive integer count");
|
|
9
|
+
const weights = isArray(parts)
|
|
10
|
+
? parts.map(w => Rational.from(w))
|
|
11
|
+
: Array.from({ length: parts }, () => Rational.from(1n));
|
|
12
|
+
const totalWeight = weights.reduce((acc, w) => acc.add(w), Rational.from(0n));
|
|
13
|
+
if (totalWeight.eq(0n))
|
|
14
|
+
throw new Error("weights must not sum to zero");
|
|
15
|
+
const inUnit = amount.in(unitSymbol);
|
|
16
|
+
const total = typeof inUnit === "bigint" ? inUnit : inUnit.floor(); //integral, so floor is exact
|
|
17
|
+
const shares = weights.map(w => Rational.from(total).mul(w).div(totalWeight));
|
|
18
|
+
const floors = shares.map(s => s.floor());
|
|
19
|
+
let leftover = total - floors.reduce((acc, f) => acc + f, 0n);
|
|
20
|
+
const byFraction = shares
|
|
21
|
+
.map((s, i) => [s.sub(floors[i]), i])
|
|
22
|
+
.sort((a, b) => a[0].eq(b[0]) ? a[1] - b[1] : a[0].gt(b[0]) ? -1 : 1);
|
|
23
|
+
const bumped = new Set();
|
|
24
|
+
for (const [, i] of byFraction) {
|
|
25
|
+
if (leftover === 0n)
|
|
26
|
+
break;
|
|
27
|
+
bumped.add(i);
|
|
28
|
+
--leftover;
|
|
29
|
+
}
|
|
30
|
+
return floors.map((f, i) => amount.ofSame(bumped.has(i) ? f + 1n : f, unitSymbol));
|
|
31
|
+
}
|
package/dist/amount.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { Brand, NarrowTo, Opts, RoArray } from "@onrail-xyz/utils";
|
|
2
|
+
import type { Rationalish, ToFixedOptions } from "./rational.js";
|
|
3
|
+
import { Rational } from "./rational.js";
|
|
4
|
+
import type { Kind, KindWithHuman, SymbolsOf, DecimalSymbolsOf, CandidateKinds } from "./kind.js";
|
|
5
|
+
import { Rate } from "./rate.js";
|
|
6
|
+
export type AmountFromArgs<K extends Kind> = [kind: K, unitSymbol: SymbolsOf<K>];
|
|
7
|
+
export declare const scalar: <const T>(base: T) => import("@onrail-xyz/utils").IsAny<T> extends true ? import("@onrail-xyz/utils").Branded<unknown, "scalar"> : import("@onrail-xyz/utils").Unbrand<T> & import("@onrail-xyz/utils").Branded<import("@onrail-xyz/utils").Unbrand<T>, "scalar" | import("@onrail-xyz/utils").ExtractTags<T>>;
|
|
8
|
+
export declare const kindOf: <K extends Kind>(amount: Amount<K>) => K;
|
|
9
|
+
export declare const isAmount: <T>(value: T) => value is NarrowTo<T, Amount<Kind>>;
|
|
10
|
+
export declare class _Amount<K extends Kind> {
|
|
11
|
+
private readonly value;
|
|
12
|
+
readonly kind: K;
|
|
13
|
+
private constructor();
|
|
14
|
+
static ofKind<const K extends KindWithHuman>(kind: K): (numericalValue: Rationalish | string, unitSymbol?: SymbolsOf<K>) => Amount<K>;
|
|
15
|
+
static ofKind<const K extends Kind>(kind: K): (numericalValue: Rationalish | string, unitSymbol: SymbolsOf<K>) => Amount<K>;
|
|
16
|
+
static from<const K extends KindWithHuman>(numericalValue: Rationalish | string, ...args: [kind: K, unitSymbol?: SymbolsOf<K>]): Amount<K>;
|
|
17
|
+
static from<const K extends Kind>(numericalValue: Rationalish | string, ...args: AmountFromArgs<K>): Amount<K>;
|
|
18
|
+
static parse<const KS extends RoArray<Kind>>(str: string, ...kinds: KS): Amount<KS[number]>;
|
|
19
|
+
static isOfKind<A extends Amount<Kind>, K extends CandidateKinds<A["kind"]>>(amt: A, kind: K): amt is NarrowTo<A, Amount<K>>;
|
|
20
|
+
static allOfKind<A extends Amount<Kind>, K extends CandidateKinds<A["kind"]>>(amts: RoArray<A>, kind: K): amts is RoArray<NarrowTo<A, Amount<K>>>;
|
|
21
|
+
zero(): this;
|
|
22
|
+
ofSame<S extends SymbolsOf<K>>(numericalValue: Rationalish | string, unitSymbol: S): this;
|
|
23
|
+
toString(): string;
|
|
24
|
+
toString(system: Extract<keyof K["systems"], string>, opts?: Opts<ToFixedOptions>): string;
|
|
25
|
+
toString(mode: "approximate" | "exact", opts?: Opts<ToFixedOptions & {
|
|
26
|
+
system: Extract<keyof K["systems"], string>;
|
|
27
|
+
}>): string;
|
|
28
|
+
toString<S extends SymbolsOf<K>>(mode: "inUnit", symbol: S, opts?: Opts<ToFixedOptions & {
|
|
29
|
+
precision: number | (S extends DecimalSymbolsOf<K> ? DecimalSymbolsOf<K> : never);
|
|
30
|
+
}>): string;
|
|
31
|
+
toJSON(): string;
|
|
32
|
+
in<S extends SymbolsOf<K>>(unitSymbol: S): S extends "atomic" ? bigint : Rational;
|
|
33
|
+
in(unitSymbol: K extends {
|
|
34
|
+
human: string;
|
|
35
|
+
} ? "human" : never): Rational;
|
|
36
|
+
in(unitSymbol: K extends {
|
|
37
|
+
atomic: string;
|
|
38
|
+
} ? "atomic" : never): bigint;
|
|
39
|
+
ceilTo<S extends SymbolsOf<K>>(unitSymbol: S): this;
|
|
40
|
+
roundTo<S extends SymbolsOf<K>>(unitSymbol: S): this;
|
|
41
|
+
floorTo<S extends SymbolsOf<K>>(unitSymbol: S): this;
|
|
42
|
+
isZero(): boolean;
|
|
43
|
+
sign(): -1 | 0 | 1;
|
|
44
|
+
eq(other: this): boolean;
|
|
45
|
+
ne(other: this): boolean;
|
|
46
|
+
lt(other: this): boolean;
|
|
47
|
+
le(other: this): boolean;
|
|
48
|
+
gt(other: this): boolean;
|
|
49
|
+
ge(other: this): boolean;
|
|
50
|
+
abs(): this;
|
|
51
|
+
neg(): this;
|
|
52
|
+
add(other: this): this;
|
|
53
|
+
sub(other: this): this;
|
|
54
|
+
mul(other: Rationalish | Amount<Brand<Kind, "scalar">>): this;
|
|
55
|
+
mul<NK extends Kind>(other: Rate<NK, K>): Amount<NK>;
|
|
56
|
+
div(other: Rationalish | Amount<Brand<Kind, "scalar">>): this;
|
|
57
|
+
div<DK extends Kind>(other: Rate<K, DK>): Amount<DK>;
|
|
58
|
+
ratio(other: this): Rational;
|
|
59
|
+
mod(other: this): this;
|
|
60
|
+
per<const DK extends Kind>(den: (KindWithHuman & DK) | Amount<DK>): Rate<K, DK>;
|
|
61
|
+
private static fromInternal;
|
|
62
|
+
private getIn;
|
|
63
|
+
private checkKind;
|
|
64
|
+
}
|
|
65
|
+
type Amount<K extends Kind> = K extends Kind ? _Amount<K> : never;
|
|
66
|
+
declare const Amount: typeof _Amount;
|
|
67
|
+
export { Amount };
|
|
68
|
+
//# sourceMappingURL=amount.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"amount.d.ts","sourceRoot":"","sources":["../src/amount.ts"],"names":[],"mappings":"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"}
|
package/dist/amount.js
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { brand } from "@onrail-xyz/utils";
|
|
2
|
+
import { Rational } from "./rational.js";
|
|
3
|
+
import { getUnit, identifyKind, sameKind } from "./kind.js";
|
|
4
|
+
import { Rate, isRate } from "./rate.js";
|
|
5
|
+
import { approximate, exact, inUnit, parse as parseFormat } from "./format.js";
|
|
6
|
+
export const scalar = brand();
|
|
7
|
+
//Free functions, not statics: a static is entered with a kind in hand (ofKind, from, parse, the
|
|
8
|
+
// *OfKind guards) and produces or narrows towards an amount of it; these are entered with an
|
|
9
|
+
// amount - or with nothing at all - and recover what the receiver's erased K cannot give back
|
|
10
|
+
// (Amount.md, "Free functions get fresh generic parameters"). rate.ts follows the same split.
|
|
11
|
+
//exact kind reads at an open K, where the `kind` property erases
|
|
12
|
+
export const kindOf = (amount) => amount.kind;
|
|
13
|
+
//class check without touching the underscored class:
|
|
14
|
+
// a union narrows to exactly its amount constituents
|
|
15
|
+
// an open Amount<K> keeps its K
|
|
16
|
+
// an unknown narrows to Amount<Kind>
|
|
17
|
+
export const isAmount = (value) => value instanceof _Amount;
|
|
18
|
+
//see end of file for the actual exports
|
|
19
|
+
//two Amount spellings:
|
|
20
|
+
// * _Amount<K> is the implementation class that's only exported for type-reachability
|
|
21
|
+
// * Amount<K> (see end of file) is the distributive alias over it. It's the only spelling that
|
|
22
|
+
// should be used by consumers
|
|
23
|
+
//
|
|
24
|
+
//Kind-preserving members return this so things match as intended, even when deferred at an open K
|
|
25
|
+
export class _Amount {
|
|
26
|
+
value;
|
|
27
|
+
kind;
|
|
28
|
+
constructor(value, kind) {
|
|
29
|
+
this.value = value;
|
|
30
|
+
this.kind = kind;
|
|
31
|
+
}
|
|
32
|
+
static ofKind(kind) {
|
|
33
|
+
return (numericalValue, unitSymbol) => _Amount.fromInternal(numericalValue, kind, unitSymbol ?? kind.human);
|
|
34
|
+
}
|
|
35
|
+
static from(numericalValue, ...args) {
|
|
36
|
+
return _Amount.fromInternal(numericalValue, args[0], args[1]);
|
|
37
|
+
}
|
|
38
|
+
//the candidates are inferred as a tuple, not as one K: a single type param would bind to the
|
|
39
|
+
// first kind and reject every other, which is the whole point of passing several
|
|
40
|
+
static parse(str, ...kinds) {
|
|
41
|
+
const kind = identifyKind(kinds, str);
|
|
42
|
+
if (!kind)
|
|
43
|
+
throw new Error("Could not identify kind from string");
|
|
44
|
+
const value = parseFormat(kind, str);
|
|
45
|
+
return _Amount.fromInternal(value, kind, kind.standard.unit);
|
|
46
|
+
}
|
|
47
|
+
static isOfKind(amt, kind) {
|
|
48
|
+
return sameKind(amt.kind, kind);
|
|
49
|
+
}
|
|
50
|
+
static allOfKind(amts, kind) {
|
|
51
|
+
return amts.every(amt => sameKind(amt.kind, kind));
|
|
52
|
+
}
|
|
53
|
+
//instance factories — "an amount of my own kind". The kind-generic replacement for reading
|
|
54
|
+
// `kind` back and feeding it to ofKind: `kind` erases at an open K, `this` does not.
|
|
55
|
+
zero() {
|
|
56
|
+
return new _Amount(Rational.from(0n), this.kind);
|
|
57
|
+
}
|
|
58
|
+
ofSame(numericalValue, unitSymbol) {
|
|
59
|
+
return _Amount.fromInternal(numericalValue, this.kind, unitSymbol);
|
|
60
|
+
}
|
|
61
|
+
toString(modeOrSys, symbolOrOpts, opts) {
|
|
62
|
+
if (modeOrSys === "inUnit") {
|
|
63
|
+
const symbol = getUnit(this.kind, symbolOrOpts).symbol;
|
|
64
|
+
const prec = typeof opts?.precision === "string"
|
|
65
|
+
? getUnit(this.kind, opts.precision).symbol
|
|
66
|
+
: opts?.precision;
|
|
67
|
+
return inUnit(this.kind, this.value, symbol, prec, opts);
|
|
68
|
+
}
|
|
69
|
+
const isMode = !modeOrSys || modeOrSys === "approximate" || modeOrSys === "exact";
|
|
70
|
+
const o = (isMode ? symbolOrOpts : { system: modeOrSys, ...symbolOrOpts });
|
|
71
|
+
return modeOrSys === "exact"
|
|
72
|
+
? exact(this.kind, this.value, o)
|
|
73
|
+
: approximate(this.kind, this.value, o);
|
|
74
|
+
}
|
|
75
|
+
toJSON() {
|
|
76
|
+
return this.toString("exact");
|
|
77
|
+
}
|
|
78
|
+
in(unitSymbol) {
|
|
79
|
+
const rat = this.getIn(unitSymbol);
|
|
80
|
+
return (unitSymbol === "atomic" ? rat.floor() : rat);
|
|
81
|
+
}
|
|
82
|
+
//the S on the unit-symbol methods might seem pointless, but it's in fact crucial:
|
|
83
|
+
// a bare SymbolsOf<K> breaks overload/implementation compatibility of the this-returning methods
|
|
84
|
+
// (the compatibility check relates _Amount<K> to _Amount<Kind>, where only same-shape generic
|
|
85
|
+
// signatures unify across the deferred SymbolsOf<K> / string constraint divide)
|
|
86
|
+
ceilTo(unitSymbol) {
|
|
87
|
+
return _Amount.fromInternal(this.getIn(unitSymbol).ceil(), this.kind, unitSymbol);
|
|
88
|
+
}
|
|
89
|
+
roundTo(unitSymbol) {
|
|
90
|
+
return _Amount.fromInternal(this.getIn(unitSymbol).round(), this.kind, unitSymbol);
|
|
91
|
+
}
|
|
92
|
+
floorTo(unitSymbol) {
|
|
93
|
+
return _Amount.fromInternal(this.getIn(unitSymbol).floor(), this.kind, unitSymbol);
|
|
94
|
+
}
|
|
95
|
+
isZero() {
|
|
96
|
+
return this.value.eq(0n);
|
|
97
|
+
}
|
|
98
|
+
sign() {
|
|
99
|
+
return this.value.sign();
|
|
100
|
+
}
|
|
101
|
+
eq(other) {
|
|
102
|
+
this.checkKind(other.kind);
|
|
103
|
+
return this.value.eq(other.value);
|
|
104
|
+
}
|
|
105
|
+
ne(other) {
|
|
106
|
+
this.checkKind(other.kind);
|
|
107
|
+
return this.value.ne(other.value);
|
|
108
|
+
}
|
|
109
|
+
lt(other) {
|
|
110
|
+
this.checkKind(other.kind);
|
|
111
|
+
return this.value.lt(other.value);
|
|
112
|
+
}
|
|
113
|
+
le(other) {
|
|
114
|
+
this.checkKind(other.kind);
|
|
115
|
+
return this.value.le(other.value);
|
|
116
|
+
}
|
|
117
|
+
gt(other) {
|
|
118
|
+
this.checkKind(other.kind);
|
|
119
|
+
return this.value.gt(other.value);
|
|
120
|
+
}
|
|
121
|
+
ge(other) {
|
|
122
|
+
this.checkKind(other.kind);
|
|
123
|
+
return this.value.ge(other.value);
|
|
124
|
+
}
|
|
125
|
+
abs() {
|
|
126
|
+
return new _Amount(this.value.abs(), this.kind);
|
|
127
|
+
}
|
|
128
|
+
neg() {
|
|
129
|
+
return new _Amount(this.value.neg(), this.kind);
|
|
130
|
+
}
|
|
131
|
+
add(other) {
|
|
132
|
+
this.checkKind(other.kind);
|
|
133
|
+
return new _Amount(this.value.add(other.value), this.kind);
|
|
134
|
+
}
|
|
135
|
+
sub(other) {
|
|
136
|
+
this.checkKind(other.kind);
|
|
137
|
+
return new _Amount(this.value.sub(other.value), this.kind);
|
|
138
|
+
}
|
|
139
|
+
mul(other) {
|
|
140
|
+
if (isRate(other)) {
|
|
141
|
+
this.checkKind(other.den);
|
|
142
|
+
return new _Amount(this.value.mul(other.ratio), other.num);
|
|
143
|
+
}
|
|
144
|
+
const rhs = isAmount(other) ? other.value : other;
|
|
145
|
+
return new _Amount(this.value.mul(rhs), this.kind);
|
|
146
|
+
}
|
|
147
|
+
div(other) {
|
|
148
|
+
if (isRate(other)) {
|
|
149
|
+
this.checkKind(other.num);
|
|
150
|
+
return new _Amount(this.value.div(other.ratio), other.den);
|
|
151
|
+
}
|
|
152
|
+
const rhs = isAmount(other) ? other.value : other;
|
|
153
|
+
return new _Amount(this.value.div(rhs), this.kind);
|
|
154
|
+
}
|
|
155
|
+
//the quotient of two amounts of one kind, which is a plain number. It is not a `div` overload
|
|
156
|
+
// because an open K may be instantiated as a union (a tranche kind known only at runtime):
|
|
157
|
+
// at K1 | K2 an overload for `this` accepts an operand of the other kind - a scalar one
|
|
158
|
+
// included, which `div` scales by - and no runtime check can tell which of the two readings
|
|
159
|
+
// the types chose. `div` takes scalars only, and this checks the kind like add and sub do
|
|
160
|
+
ratio(other) {
|
|
161
|
+
this.checkKind(other.kind);
|
|
162
|
+
return this.value.div(other.value);
|
|
163
|
+
}
|
|
164
|
+
mod(other) {
|
|
165
|
+
this.checkKind(other.kind);
|
|
166
|
+
return new _Amount(this.value.mod(other.value), this.kind);
|
|
167
|
+
}
|
|
168
|
+
per(den) {
|
|
169
|
+
return Rate.from(this, den);
|
|
170
|
+
}
|
|
171
|
+
static fromInternal(numericalValue, kind, unitSymbol) {
|
|
172
|
+
const unit = getUnit(kind, (unitSymbol ?? "human"));
|
|
173
|
+
return new _Amount(Rational.from(numericalValue).mul(unit.scale), kind);
|
|
174
|
+
}
|
|
175
|
+
getIn(unitSymbol) {
|
|
176
|
+
return this.value.div(getUnit(this.kind, unitSymbol).scale);
|
|
177
|
+
}
|
|
178
|
+
checkKind(otherKind) {
|
|
179
|
+
if (!sameKind(this.kind, otherKind))
|
|
180
|
+
throw new Error(`Kind mismatch: ${this.kind.name} vs ${otherKind.name}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const Amount = _Amount;
|
|
184
|
+
export { Amount };
|
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Opts } from "@onrail-xyz/utils";
|
|
2
|
+
import { type ToFixedOptions, Rational } from "./rational.js";
|
|
3
|
+
import type { Kind, Unit, KindUnitSymbols } from "./kind.js";
|
|
4
|
+
export declare function inUnit<K extends Kind>(kind: K, stdVal: Rational, symbol: KindUnitSymbols<K>, precision?: number | KindUnitSymbols<K>, opts?: Opts<ToFixedOptions>): string;
|
|
5
|
+
export declare function inUnitApprox<K extends Kind>(kind: K, stdVal: Rational, symbol: KindUnitSymbols<K>, opts?: Opts<ToFixedOptions>): string;
|
|
6
|
+
export declare function approximate(kind: Kind, stdVal: Rational, opts?: Opts<ToFixedOptions & {
|
|
7
|
+
system: string;
|
|
8
|
+
}>): string;
|
|
9
|
+
export declare function exact(kind: Kind, stdVal: Rational, opts?: Opts<ToFixedOptions & {
|
|
10
|
+
system: string;
|
|
11
|
+
}>): string;
|
|
12
|
+
export declare function parse(kind: Kind, str: string): Rational;
|
|
13
|
+
export declare function exactRate(num: Kind, den: Kind, ratio: Rational, opts?: Opts<ToFixedOptions>): string;
|
|
14
|
+
export declare const bestDenUnit: (num: Kind, den: Kind, ratio: Rational) => Unit;
|
|
15
|
+
//# sourceMappingURL=format.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAW,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,KAAK,cAAc,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC9D,OAAO,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAK7D,wBAAgB,MAAM,CAAC,CAAC,SAAS,IAAI,EACnC,IAAI,EAAO,CAAC,EACZ,MAAM,EAAK,QAAQ,EACnB,MAAM,EAAK,eAAe,CAAC,CAAC,CAAC,EAC7B,SAAS,GAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAK,EAC1C,IAAI,CAAC,EAAM,IAAI,CAAC,cAAc,CAAC,GAC9B,MAAM,CAWR;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,IAAI,EACzC,IAAI,EAAI,CAAC,EACT,MAAM,EAAE,QAAQ,EAChB,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC,EAC1B,IAAI,CAAC,EAAG,IAAI,CAAC,cAAc,CAAC,GAC3B,MAAM,CAKR;AAED,wBAAgB,WAAW,CACzB,IAAI,EAAI,IAAI,EACZ,MAAM,EAAE,QAAQ,EAChB,IAAI,CAAC,EAAG,IAAI,CAAC,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAChD,MAAM,CAiBR;AAcD,wBAAgB,KAAK,CACnB,IAAI,EAAI,IAAI,EACZ,MAAM,EAAE,QAAQ,EAChB,IAAI,CAAC,EAAG,IAAI,CAAC,cAAc,GAAG;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAChD,MAAM,CAUR;AAED,wBAAgB,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG,QAAQ,CAmCvD;AAED,wBAAgB,SAAS,CACvB,GAAG,EAAI,IAAI,EACX,GAAG,EAAI,IAAI,EACX,KAAK,EAAE,QAAQ,EACf,IAAI,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC,GAC1B,MAAM,CAGR;AAED,eAAO,MAAM,WAAW,QAAS,IAAI,OAAO,IAAI,SAAS,QAAQ,KAAG,IAcnE,CAAC"}
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { Rational } from "./rational.js";
|
|
2
|
+
import { findUnit, segment } from "./segmenting.js";
|
|
3
|
+
const approxDigits = 3;
|
|
4
|
+
export function inUnit(kind, stdVal, symbol, precision = 0, opts) {
|
|
5
|
+
const unit = kind.units[symbol];
|
|
6
|
+
const value = stdVal.div(unit.scale);
|
|
7
|
+
const decimals = typeof precision === "number"
|
|
8
|
+
? precision
|
|
9
|
+
: unit.oom - kind.units[precision].oom;
|
|
10
|
+
if (decimals < 0)
|
|
11
|
+
throw new Error("Precision must be non-negative");
|
|
12
|
+
return formatWithSymbol(value, decimals, unit, opts);
|
|
13
|
+
}
|
|
14
|
+
export function inUnitApprox(kind, stdVal, symbol, opts) {
|
|
15
|
+
const unit = kind.units[symbol];
|
|
16
|
+
const value = stdVal.div(unit.scale);
|
|
17
|
+
return formatWithSymbol(value, sigDigitPrec(value), unit, opts);
|
|
18
|
+
}
|
|
19
|
+
export function approximate(kind, stdVal, opts) {
|
|
20
|
+
const sortedByScale = getSystemUnits(kind, opts?.system);
|
|
21
|
+
if (!isDecimal(kind, opts?.system))
|
|
22
|
+
return compound(kind, sortedByScale, stdVal, true, opts);
|
|
23
|
+
if (stdVal.eq(0))
|
|
24
|
+
return formatZero(kind, sortedByScale, opts);
|
|
25
|
+
const reading = approxReading(stdVal, sortedByScale);
|
|
26
|
+
//a reading that rounds up into the next unit's range renders there instead: $0.9996 reads $1,
|
|
27
|
+
// not 100c
|
|
28
|
+
const rounded = roundTo(reading.value, reading.precision).mul(reading.unit.scale);
|
|
29
|
+
const next = sortedByScale[reading.index - 1];
|
|
30
|
+
const final = reading.index > 0 && next !== undefined && rounded.abs().ge(next.scale)
|
|
31
|
+
? approxReading(rounded, sortedByScale)
|
|
32
|
+
: reading;
|
|
33
|
+
return formatWithSymbol(final.value, final.precision, final.unit, opts);
|
|
34
|
+
}
|
|
35
|
+
const approxReading = (stdVal, sortedByScale) => {
|
|
36
|
+
const { index, unit, value, promoted } = displayUnitInfo(stdVal, sortedByScale);
|
|
37
|
+
const precision = promoted ? approxDigits : getApproxPrec(index, value);
|
|
38
|
+
return { index, unit, value, precision };
|
|
39
|
+
};
|
|
40
|
+
//the value as a given number of decimals renders it (half away from zero, as toFixed does)
|
|
41
|
+
const roundTo = (value, decimals) => {
|
|
42
|
+
const scale = Rational.powerOfTen(decimals);
|
|
43
|
+
return Rational.from(value.mul(scale).round()).div(scale);
|
|
44
|
+
};
|
|
45
|
+
export function exact(kind, stdVal, opts) {
|
|
46
|
+
const sortedByScale = getSystemUnits(kind, opts?.system);
|
|
47
|
+
if (!isDecimal(kind, opts?.system))
|
|
48
|
+
return compound(kind, sortedByScale, stdVal, false, opts);
|
|
49
|
+
if (stdVal.eq(0))
|
|
50
|
+
return formatZero(kind, sortedByScale, opts);
|
|
51
|
+
const { unit, value } = displayUnitInfo(stdVal, sortedByScale);
|
|
52
|
+
return renderExact(value, unit, opts);
|
|
53
|
+
}
|
|
54
|
+
export function parse(kind, str) {
|
|
55
|
+
const processPair = (pair) => {
|
|
56
|
+
const value = Rational.from(str.substring(pair.value[0], pair.value[1]));
|
|
57
|
+
const symbol = str.substring(pair.symbol[0], pair.symbol[1]);
|
|
58
|
+
const unit = findUnit(kind.units, symbol);
|
|
59
|
+
if (!unit)
|
|
60
|
+
throw new Error(`Unknown unit: ${symbol}`);
|
|
61
|
+
return { value, unit };
|
|
62
|
+
};
|
|
63
|
+
const { negative, pairs } = segment(str);
|
|
64
|
+
if (pairs.length === 1) {
|
|
65
|
+
const { value, unit } = processPair(pairs[0]);
|
|
66
|
+
const absVal = value.mul(unit.scale);
|
|
67
|
+
return negative ? absVal.neg() : absVal;
|
|
68
|
+
}
|
|
69
|
+
let total = Rational.from(0n);
|
|
70
|
+
let lastScale = undefined;
|
|
71
|
+
for (let i = 0; i < pairs.length; ++i) {
|
|
72
|
+
const { value, unit } = processPair(pairs[i]);
|
|
73
|
+
if (i !== pairs.length - 1 && !value.isInteger())
|
|
74
|
+
throw new Error("Decimal only allowed in final unit");
|
|
75
|
+
if (lastScale && lastScale.le(unit.scale))
|
|
76
|
+
throw new Error("Units must be in descending order");
|
|
77
|
+
total = total.add(value.mul(unit.scale));
|
|
78
|
+
lastScale = unit.scale;
|
|
79
|
+
}
|
|
80
|
+
return negative ? total.neg() : total;
|
|
81
|
+
}
|
|
82
|
+
export function exactRate(num, den, ratio, opts) {
|
|
83
|
+
const denUnit = bestDenUnit(num, den, ratio);
|
|
84
|
+
return `${exact(num, ratio.mul(denUnit.scale), opts)}/${denUnit.symbol}`;
|
|
85
|
+
}
|
|
86
|
+
export const bestDenUnit = (num, den, ratio) => {
|
|
87
|
+
if (den.human !== undefined || ratio.eq(0))
|
|
88
|
+
return den.units[den.human ?? den.standard.unit];
|
|
89
|
+
let best;
|
|
90
|
+
for (const unit of getSystemUnits(den)) {
|
|
91
|
+
const rendered = exact(num, ratio.mul(unit.scale), { thousandsSep: "" });
|
|
92
|
+
//symbols cannot contain "/", so its presence marks the mixed-number form
|
|
93
|
+
const mixed = rendered.includes("/");
|
|
94
|
+
const size = rendered.length + unit.symbol.length;
|
|
95
|
+
if (!best || (mixed === best.mixed ? size < best.size : best.mixed))
|
|
96
|
+
best = { mixed, size, unit };
|
|
97
|
+
}
|
|
98
|
+
return best.unit;
|
|
99
|
+
};
|
|
100
|
+
const renderExact = (value, unit, opts) => {
|
|
101
|
+
const valStr = value.toString({ thousandsSep: opts?.thousandsSep ?? "," });
|
|
102
|
+
return attachSymbol(valStr, unit, valStr === "1" || valStr === "-1");
|
|
103
|
+
};
|
|
104
|
+
const compound = (kind, sortedByScale, stdVal, approx, opts) => {
|
|
105
|
+
if (stdVal.eq(0))
|
|
106
|
+
return formatZero(kind, sortedByScale, opts);
|
|
107
|
+
const sign = stdVal.lt(0) ? "-" : "";
|
|
108
|
+
const smallest = sortedByScale.length - 1;
|
|
109
|
+
let absStdVal = stdVal.abs();
|
|
110
|
+
if (approx) {
|
|
111
|
+
//a value the smallest unit carries on its own renders in it, to significant digits - unless
|
|
112
|
+
// those round up to the next unit, in which case it spans units after all
|
|
113
|
+
const { lbIndex, lbUnit, lbValue } = lowerBoundInfo(absStdVal, sortedByScale);
|
|
114
|
+
if (lbIndex === -1 || lbIndex === smallest) {
|
|
115
|
+
const precision = getApproxPrec(lbIndex, lbValue);
|
|
116
|
+
const rounded = roundTo(lbValue, precision).mul(lbUnit.scale);
|
|
117
|
+
if (smallest === 0 || lbIndex === -1 || rounded.lt(sortedByScale[smallest - 1].scale))
|
|
118
|
+
return sign + formatWithSymbol(lbValue, precision, lbUnit, opts);
|
|
119
|
+
absStdVal = rounded;
|
|
120
|
+
}
|
|
121
|
+
//rounded to whole smallest units first and decomposed exactly after, so no part can round
|
|
122
|
+
// up into the next unit's range (no "1 minute 60 seconds")
|
|
123
|
+
const smallestScale = sortedByScale[smallest].scale;
|
|
124
|
+
absStdVal = Rational.from(absStdVal.div(smallestScale).round()).mul(smallestScale);
|
|
125
|
+
}
|
|
126
|
+
const parts = [];
|
|
127
|
+
const tolerance = approx ? absStdVal.div(Rational.powerOfTen(approxDigits)) : null;
|
|
128
|
+
let remainder = absStdVal;
|
|
129
|
+
let startFrom = 0;
|
|
130
|
+
while (remainder.ne(0)) {
|
|
131
|
+
const { lbIndex, lbUnit, lbValue } = lowerBoundInfo(remainder, sortedByScale, startFrom);
|
|
132
|
+
const atSmallest = lbIndex === -1 || lbIndex === smallest;
|
|
133
|
+
if (atSmallest && !approx)
|
|
134
|
+
parts.push(renderExact(lbValue, lbUnit, opts));
|
|
135
|
+
else
|
|
136
|
+
parts.push(formatWithSymbol(Rational.from(lbValue.floor()), 0, lbUnit, opts));
|
|
137
|
+
if (atSmallest)
|
|
138
|
+
break;
|
|
139
|
+
const nextRemainder = remainder.mod(lbUnit.scale);
|
|
140
|
+
if (tolerance !== null && nextRemainder.lt(tolerance))
|
|
141
|
+
break;
|
|
142
|
+
remainder = nextRemainder;
|
|
143
|
+
startFrom = lbIndex + 1;
|
|
144
|
+
}
|
|
145
|
+
return sign + parts.join(" ");
|
|
146
|
+
};
|
|
147
|
+
const formatZero = (kind, sortedByScale, opts) => {
|
|
148
|
+
const preferred = kind.units[kind.human ?? kind.standard.unit];
|
|
149
|
+
const unit = sortedByScale.find(u => u.scale.eq(preferred.scale)) ?? sortedByScale.at(-1);
|
|
150
|
+
return formatWithSymbol(Rational.from(0n), 0, unit, opts);
|
|
151
|
+
};
|
|
152
|
+
function formatWithSymbol(value, decimals, unit, opts) {
|
|
153
|
+
const thousandsSep = opts?.thousandsSep ?? ",";
|
|
154
|
+
const trimZeros = opts?.trimZeros ?? true;
|
|
155
|
+
const valStr = value.toFixed(decimals, { thousandsSep, trimZeros });
|
|
156
|
+
//the plural follows the displayed magnitude, not the exact one (0.9999 at 2 decimals shows as 1)
|
|
157
|
+
const oneStr = Rational.from(1n).toFixed(decimals, { thousandsSep, trimZeros });
|
|
158
|
+
return attachSymbol(valStr, unit, valStr === oneStr || valStr === `-${oneStr}`);
|
|
159
|
+
}
|
|
160
|
+
const attachSymbol = (valStr, unit, singular) => {
|
|
161
|
+
const sym = unit.plural && !singular ? unit.plural : unit.symbol;
|
|
162
|
+
const space = (unit.spacing ?? "spaced") === "spaced" ? " " : "";
|
|
163
|
+
if ((unit.position ?? "postfix") === "postfix")
|
|
164
|
+
return valStr + space + sym;
|
|
165
|
+
//the sign stays outside the symbol: parsing only takes a "-" that leads the whole pair, so an
|
|
166
|
+
// interior one ("$-100") would be read as part of the symbol
|
|
167
|
+
const [sign, digits] = valStr.startsWith("-") ? ["-", valStr.slice(1)] : ["", valStr];
|
|
168
|
+
return sign + sym + space + digits;
|
|
169
|
+
};
|
|
170
|
+
const getSystem = (kind, system) => {
|
|
171
|
+
const name = system ?? kind.standard.system;
|
|
172
|
+
const sys = kind.systems[name];
|
|
173
|
+
if (!sys)
|
|
174
|
+
throw new Error(`Unknown system: ${name}`);
|
|
175
|
+
return sys;
|
|
176
|
+
};
|
|
177
|
+
const getSystemUnits = (kind, system) => {
|
|
178
|
+
const units = [];
|
|
179
|
+
for (const symbol of getSystem(kind, system).symbols) {
|
|
180
|
+
const unit = kind.units[symbol];
|
|
181
|
+
if (!units.some(u => u.scale.eq(unit.scale)))
|
|
182
|
+
units.push(unit);
|
|
183
|
+
}
|
|
184
|
+
return units;
|
|
185
|
+
};
|
|
186
|
+
const isDecimal = (kind, system) => getSystem(kind, system).decimal;
|
|
187
|
+
//number of decimals that yields approxDigits significant digits (0 for values >= 10^approxDigits)
|
|
188
|
+
const sigDigitPrec = (value) => {
|
|
189
|
+
const absVal = value.abs();
|
|
190
|
+
let precision = approxDigits;
|
|
191
|
+
while (precision > 0 && absVal.ge(Rational.powerOfTen(approxDigits - precision)))
|
|
192
|
+
--precision;
|
|
193
|
+
while (absVal.ne(0) && absVal.lt(Rational.powerOfTen(approxDigits - precision - 1)))
|
|
194
|
+
++precision;
|
|
195
|
+
return precision;
|
|
196
|
+
};
|
|
197
|
+
//below the smallest unit of the kind the precision is capped instead of following the value
|
|
198
|
+
const getApproxPrec = (lbIndex, lbValue) => lbIndex === -1 ? approxDigits : sigDigitPrec(lbValue);
|
|
199
|
+
//the unit both display modes render in: the largest unit the value reaches, promoted to the next
|
|
200
|
+
// one up when the ladder gap is wide enough for the reading to run long (six orders — ¢/$ never
|
|
201
|
+
// promotes) and the promoted reading has at least a thousandth of it: 15,000 Gwei stays put,
|
|
202
|
+
// 0.5 ETH is not 500,000,000 Gwei
|
|
203
|
+
const displayUnitInfo = (stdVal, sortedByScale) => {
|
|
204
|
+
const { lbIndex, lbUnit, lbValue } = lowerBoundInfo(stdVal, sortedByScale);
|
|
205
|
+
const lb = { index: lbIndex, unit: lbUnit, value: lbValue, promoted: false };
|
|
206
|
+
if (lbIndex <= 0)
|
|
207
|
+
return lb;
|
|
208
|
+
const unit = sortedByScale[lbIndex - 1];
|
|
209
|
+
const value = stdVal.div(unit.scale);
|
|
210
|
+
return unit.oom - lbUnit.oom >= 2 * approxDigits
|
|
211
|
+
&& value.abs().ge(Rational.powerOfTen(-approxDigits))
|
|
212
|
+
? { index: lbIndex - 1, unit, value, promoted: true }
|
|
213
|
+
: lb;
|
|
214
|
+
};
|
|
215
|
+
const lowerBoundInfo = (stdVal, sortedByScale, startIndex = 0) => {
|
|
216
|
+
const absStdVal = stdVal.abs();
|
|
217
|
+
let lbIndex = startIndex;
|
|
218
|
+
for (; lbIndex < sortedByScale.length; ++lbIndex)
|
|
219
|
+
if (absStdVal.ge(sortedByScale[lbIndex].scale))
|
|
220
|
+
break;
|
|
221
|
+
if (lbIndex === sortedByScale.length)
|
|
222
|
+
lbIndex = -1;
|
|
223
|
+
const lbUnit = sortedByScale.at(lbIndex);
|
|
224
|
+
const lbValue = stdVal.div(lbUnit.scale);
|
|
225
|
+
return { lbIndex, lbUnit, lbValue };
|
|
226
|
+
};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from "./rational.js";
|
|
2
|
+
export * from "./kind.js";
|
|
3
|
+
export * from "./unitSpecs.js";
|
|
4
|
+
export * from "./amount.js";
|
|
5
|
+
export * from "./rate.js";
|
|
6
|
+
export * from "./aggregating.js";
|
|
7
|
+
export * from "./allocating.js";
|
|
8
|
+
export * from "./jsonCodecs.js";
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,WAAW,CAAC;AAC1B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,kBAAkB,CAAC;AACjC,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { JsonCodec, MaybeArray } from "@onrail-xyz/utils";
|
|
2
|
+
import { Rational } from "./rational.js";
|
|
3
|
+
import type { Kind } from "./kind.js";
|
|
4
|
+
import { Amount } from "./amount.js";
|
|
5
|
+
import { Rate } from "./rate.js";
|
|
6
|
+
export declare const rationalCodec: JsonCodec<Rational>;
|
|
7
|
+
export declare const amountCodec: (kinds: MaybeArray<Kind>) => JsonCodec<Amount<Kind>>;
|
|
8
|
+
export declare const rateCodec: (numKinds: MaybeArray<Kind>, denKinds: MaybeArray<Kind>) => JsonCodec<Rate<Kind, Kind>>;
|
|
9
|
+
//# sourceMappingURL=jsonCodecs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonCodecs.d.ts","sourceRoot":"","sources":["../src/jsonCodecs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAsB,MAAM,mBAAmB,CAAC;AAEnF,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,MAAM,EAAY,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,IAAI,EAAU,MAAM,WAAW,CAAC;AA6BzC,eAAO,MAAM,aAAa,EAAE,SAAS,CAAC,QAAQ,CAK7C,CAAC;AAEF,eAAO,MAAM,WAAW,UAAW,UAAU,CAAC,IAAI,CAAC,KAAG,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,CAQ1E,CAAC;AAEH,eAAO,MAAM,SAAS,aACV,UAAU,CAAC,IAAI,CAAC,YAChB,UAAU,CAAC,IAAI,CAAC,KACzB,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAQ3B,CAAC"}
|