@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
package/src/kind.ts
ADDED
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import type { Opts, RoArray, RoNeTuple, RoPair, Brand,
|
|
2
|
+
If, IsAny, IsNever, Extends, OptionalArg } from "@onrail-xyz/utils";
|
|
3
|
+
import { omit, fromEntries, pick, isArray } from "@onrail-xyz/utils";
|
|
4
|
+
import { type Rationalish, Rational } from "./rational.js";
|
|
5
|
+
import { findUnit, segment, symbolKey } from "./segmenting.js";
|
|
6
|
+
|
|
7
|
+
// ---- Public API types ----
|
|
8
|
+
|
|
9
|
+
export type SymbolSpec = Readonly<{
|
|
10
|
+
symbol: string;
|
|
11
|
+
plural?: string;
|
|
12
|
+
position?: "postfix" | "prefix";
|
|
13
|
+
spacing?: "spaced" | "compact";
|
|
14
|
+
}>;
|
|
15
|
+
|
|
16
|
+
export type DecimalSpec<N extends number = number> =
|
|
17
|
+
{ readonly oom: N; readonly scale?: never };
|
|
18
|
+
|
|
19
|
+
export type ScaleSpec<N extends Rationalish = Rationalish> =
|
|
20
|
+
{ readonly oom?: never; readonly scale: N };
|
|
21
|
+
|
|
22
|
+
export type Unit = SymbolSpec & { readonly scale: Rational; readonly oom?: number };
|
|
23
|
+
|
|
24
|
+
export type SystemInfo<
|
|
25
|
+
N extends string = string,
|
|
26
|
+
U extends string = string,
|
|
27
|
+
D extends boolean = boolean,
|
|
28
|
+
> = { name: N; symbols: U; decimal: D };
|
|
29
|
+
|
|
30
|
+
export type StandardInfo<
|
|
31
|
+
SY extends string = string,
|
|
32
|
+
SU extends string = string,
|
|
33
|
+
> = { system: SY; unit: SU };
|
|
34
|
+
|
|
35
|
+
export type ValidStandardInfo<Y extends SystemInfo> =
|
|
36
|
+
Y extends SystemInfo<infer N, infer U, boolean> ? StandardInfo<N, U> : never;
|
|
37
|
+
|
|
38
|
+
export type Kind<
|
|
39
|
+
U extends string = string,
|
|
40
|
+
N extends string = string,
|
|
41
|
+
Y extends SystemInfo = SystemInfo,
|
|
42
|
+
S extends ValidStandardInfo<Y> = ValidStandardInfo<Y>,
|
|
43
|
+
H extends U | undefined = U | undefined,
|
|
44
|
+
A extends U | undefined = U | undefined,
|
|
45
|
+
> = {
|
|
46
|
+
readonly name: N;
|
|
47
|
+
readonly units: KindUnits<U>; //guaranteed to be in descending order by scale
|
|
48
|
+
readonly standard: S;
|
|
49
|
+
readonly systems: KindSystems<Y>;
|
|
50
|
+
} & (
|
|
51
|
+
[U | undefined] extends [H]
|
|
52
|
+
? { readonly human?: U }
|
|
53
|
+
: [H] extends [U]
|
|
54
|
+
? { readonly human: H }
|
|
55
|
+
: [H] extends [undefined]
|
|
56
|
+
? unknown
|
|
57
|
+
: { readonly human?: Exclude<H, undefined> }
|
|
58
|
+
) & (
|
|
59
|
+
[U | undefined] extends [A]
|
|
60
|
+
? { readonly atomic?: U }
|
|
61
|
+
: [A] extends [U]
|
|
62
|
+
? { readonly atomic: A }
|
|
63
|
+
: [A] extends [undefined]
|
|
64
|
+
? unknown
|
|
65
|
+
: { readonly atomic?: Exclude<A, undefined> }
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
export type KindWithHuman<
|
|
69
|
+
U extends string = string,
|
|
70
|
+
N extends string = string,
|
|
71
|
+
Y extends SystemInfo = SystemInfo,
|
|
72
|
+
S extends ValidStandardInfo<Y> = ValidStandardInfo<Y>,
|
|
73
|
+
H extends U = U,
|
|
74
|
+
A extends U | undefined = U | undefined,
|
|
75
|
+
> = Kind<U, N, Y, S, H, A>;
|
|
76
|
+
|
|
77
|
+
export type KindWithAtomic<
|
|
78
|
+
U extends string = string,
|
|
79
|
+
N extends string = string,
|
|
80
|
+
Y extends SystemInfo = SystemInfo,
|
|
81
|
+
S extends ValidStandardInfo<Y> = ValidStandardInfo<Y>,
|
|
82
|
+
H extends U | undefined = U | undefined,
|
|
83
|
+
A extends U = U,
|
|
84
|
+
> = Kind<U, N, Y, S, H, A>;
|
|
85
|
+
|
|
86
|
+
export type KindWithHumanAndAtomic<
|
|
87
|
+
U extends string = string,
|
|
88
|
+
N extends string = string,
|
|
89
|
+
Y extends SystemInfo = SystemInfo,
|
|
90
|
+
S extends ValidStandardInfo<Y> = ValidStandardInfo<Y>,
|
|
91
|
+
H extends U = U,
|
|
92
|
+
A extends U = U,
|
|
93
|
+
> = Kind<U, N, Y, S, H, A>;
|
|
94
|
+
|
|
95
|
+
export type KindWithDecimalHumanAndAtomic<
|
|
96
|
+
U extends string = string,
|
|
97
|
+
N extends string = string,
|
|
98
|
+
Y extends SystemInfo = SystemInfo,
|
|
99
|
+
S extends ValidStandardInfo<Y> = ValidStandardInfo<Y>,
|
|
100
|
+
H extends U = U,
|
|
101
|
+
A extends U = U,
|
|
102
|
+
> = Brand<Kind<U, N, Y, S, H, A>, "decimalHuman" | "decimalAtomic">;
|
|
103
|
+
|
|
104
|
+
export type KindUnitSymbols<K extends Kind> = Extract<keyof K["units"], string>;
|
|
105
|
+
|
|
106
|
+
type MetaSymbolsOf<K extends Kind> =
|
|
107
|
+
"standard" |
|
|
108
|
+
(K["human"] extends string ? "human" : never) |
|
|
109
|
+
(K["atomic"] extends string ? "atomic" : never);
|
|
110
|
+
|
|
111
|
+
export type SymbolsOf<K extends Kind> =
|
|
112
|
+
If<IsAny<K>,
|
|
113
|
+
string,
|
|
114
|
+
If<Extends<string, KindUnitSymbols<K>>,
|
|
115
|
+
If<IsNever<("human" | "atomic") & MetaSymbolsOf<K>>,
|
|
116
|
+
string,
|
|
117
|
+
never
|
|
118
|
+
>,
|
|
119
|
+
KindUnitSymbols<K>
|
|
120
|
+
>
|
|
121
|
+
> | MetaSymbolsOf<K>;
|
|
122
|
+
|
|
123
|
+
export type DecimalSymbolsOf<K extends Kind> = DecimalUnitSymbols<K> | DecimalMetaSymbolOf<K>;
|
|
124
|
+
|
|
125
|
+
export type ResolvedSymbolOf<K extends Kind, M extends SymbolsOf<K>> =
|
|
126
|
+
M extends "standard" ? K["standard"]["unit"] :
|
|
127
|
+
M extends "human" ? K["human"] :
|
|
128
|
+
M extends "atomic" ? K["atomic"] :
|
|
129
|
+
M;
|
|
130
|
+
|
|
131
|
+
export type IsBareKind<K extends Kind> = Extends<string, K["name"]>;
|
|
132
|
+
|
|
133
|
+
export type CandidateKinds<K extends Kind> = If<IsBareKind<K>, Kind, K>;
|
|
134
|
+
|
|
135
|
+
export function sameKind(a: Kind, b: Kind): boolean {
|
|
136
|
+
return a === b || a.name === b.name;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function getUnit<
|
|
140
|
+
const K extends Kind,
|
|
141
|
+
S extends SymbolsOf<K>,
|
|
142
|
+
>(kind: K, unitSymbol: S): Unit {
|
|
143
|
+
const resolved =
|
|
144
|
+
unitSymbol === "standard" ? kind.standard.unit :
|
|
145
|
+
unitSymbol === "human" ? kind.human :
|
|
146
|
+
unitSymbol === "atomic" ? kind.atomic :
|
|
147
|
+
unitSymbol;
|
|
148
|
+
|
|
149
|
+
const unit = resolved === undefined ? undefined : kind.units[resolved];
|
|
150
|
+
if (!unit)
|
|
151
|
+
throw new Error(`Kind ${kind.name} has no unit ${unitSymbol}`);
|
|
152
|
+
|
|
153
|
+
return unit;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function getDecimals<const K extends KindWithDecimalHumanAndAtomic>(
|
|
157
|
+
kind: K,
|
|
158
|
+
opts?: GetDecimalsOpts<K>,
|
|
159
|
+
): number;
|
|
160
|
+
export function getDecimals<const K extends Kind>(
|
|
161
|
+
kind: K,
|
|
162
|
+
opts: GetDecimalsOpts<K>,
|
|
163
|
+
): number;
|
|
164
|
+
export function getDecimals(
|
|
165
|
+
kind: Kind,
|
|
166
|
+
opts?: Opts<{ of: string; in: string }>,
|
|
167
|
+
): number {
|
|
168
|
+
const { of: ofSymbol = "human", in: inSymbol = "atomic" } = opts ?? {};
|
|
169
|
+
const oomOf = (symbol: string) => {
|
|
170
|
+
const { oom } = getUnit(kind, symbol);
|
|
171
|
+
if (oom === undefined)
|
|
172
|
+
throw new Error(`Unit ${symbol} of kind ${kind.name} is not decimal`);
|
|
173
|
+
|
|
174
|
+
return oom;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
return oomOf(ofSymbol) - oomOf(inSymbol);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
type MetaBrandTags<H, A, S, I extends KindUnitsInput> =
|
|
181
|
+
[H, "human"] | [A, "atomic"] | [S, "standard"] extends infer M
|
|
182
|
+
? M extends [DecimalSymbolsOfSystem<SystemInfoOf<I>>, MetaSymbols]
|
|
183
|
+
? `decimal${Capitalize<M[1]>}`
|
|
184
|
+
: ""
|
|
185
|
+
: never;
|
|
186
|
+
|
|
187
|
+
type KindOpts<I extends KindUnitsInput> = Opts<{
|
|
188
|
+
human: UnitSymbolsOf<UnitsSpecOf<I>>;
|
|
189
|
+
atomic: UnitSymbolsOf<UnitsSpecOf<I>>;
|
|
190
|
+
}>;
|
|
191
|
+
//a meta symbol read off the whole record: one that may be undefined - or absent, where the
|
|
192
|
+
// record's own type declares it optional - may be unset on the kind
|
|
193
|
+
type MetaOf<O, K extends "human" | "atomic"> = K extends keyof O ? O[K] : undefined;
|
|
194
|
+
|
|
195
|
+
export function kind<
|
|
196
|
+
N extends string,
|
|
197
|
+
const I extends KindUnitsInput,
|
|
198
|
+
const O extends KindOpts<I> = {},
|
|
199
|
+
>(name: N,
|
|
200
|
+
unitsInput: I,
|
|
201
|
+
...[opts]: OptionalArg<O>
|
|
202
|
+
): Brand<
|
|
203
|
+
Kind<
|
|
204
|
+
UnitSymbolsOf<UnitsSpecOf<I>>,
|
|
205
|
+
N,
|
|
206
|
+
SystemInfoOf<I>,
|
|
207
|
+
StandardInfoOf<I>,
|
|
208
|
+
MetaOf<O, "human">,
|
|
209
|
+
MetaOf<O, "atomic">
|
|
210
|
+
>,
|
|
211
|
+
MetaBrandTags<MetaOf<O, "human">, MetaOf<O, "atomic">, StandardInfoOf<I>["unit"], I>
|
|
212
|
+
> {
|
|
213
|
+
const isSystemsSpec = isArray(unitsInput) && isArray(unitsInput[0]);
|
|
214
|
+
const systemEntries = (
|
|
215
|
+
isSystemsSpec ? unitsInput : [["default", unitsInput]]
|
|
216
|
+
) as RoArray<SystemEntry>;
|
|
217
|
+
|
|
218
|
+
const firstSpec = systemEntries[0]![1];
|
|
219
|
+
const standard = {
|
|
220
|
+
system: systemEntries[0]![0],
|
|
221
|
+
unit: firstSpec[0]!.symbols[0]!.symbol,
|
|
222
|
+
} as const;
|
|
223
|
+
const meta = pick<KindOpts<I>, ["human", "atomic"]>(opts ?? {}, ["human", "atomic"]);
|
|
224
|
+
|
|
225
|
+
const allUnits: [string, Unit][] = [];
|
|
226
|
+
const systems: Record<string, { symbols: string[]; decimal: boolean }> = {};
|
|
227
|
+
|
|
228
|
+
for (let i = 0; i < systemEntries.length; ++i) {
|
|
229
|
+
const [sysName, unitsSpec] = systemEntries[i]!;
|
|
230
|
+
if (displayModes.some(mode => mode === sysName))
|
|
231
|
+
throw new Error(`"${sysName}" is a display mode and cannot name a system`);
|
|
232
|
+
|
|
233
|
+
//mirrors IsSystemDecimal: an explicit oom/scale on the first unit decides; a bare first
|
|
234
|
+
// unit defers to the second, and a lone bare unit counts as decimal
|
|
235
|
+
const sysDecimal = i === 0
|
|
236
|
+
? "oom" in firstSpec[0]!
|
|
237
|
+
? true
|
|
238
|
+
: "scale" in firstSpec[0]!
|
|
239
|
+
? false
|
|
240
|
+
: firstSpec.length === 1 || "oom" in firstSpec[1]!
|
|
241
|
+
: unitsSpec.every(u => "oom" in u);
|
|
242
|
+
|
|
243
|
+
if (Object.hasOwn(systems, sysName))
|
|
244
|
+
throw new Error(`Duplicate system "${sysName}"`);
|
|
245
|
+
|
|
246
|
+
systems[sysName] = { symbols: [], decimal: sysDecimal };
|
|
247
|
+
|
|
248
|
+
const processed = unitsSpec
|
|
249
|
+
.map(u => ({ ...u, ...ensureScale(sysDecimal, u) }) as UnitBaseSpec & { scale: Rational })
|
|
250
|
+
.sort((a, b) => b.scale.gt(a.scale) ? 1 : b.scale.lt(a.scale) ? -1 : 0);
|
|
251
|
+
|
|
252
|
+
for (const unit of processed) {
|
|
253
|
+
if (unit.scale.sign() !== 1)
|
|
254
|
+
throw new Error(`Unit scales must be positive, got ${unit.scale.toString()}`);
|
|
255
|
+
|
|
256
|
+
const magScale = omit(unit, "symbols");
|
|
257
|
+
for (const symbolSpec of unit.symbols) {
|
|
258
|
+
const unitData = { ...symbolSpec, ...magScale };
|
|
259
|
+
const symbols = [
|
|
260
|
+
symbolSpec.symbol,
|
|
261
|
+
...(symbolSpec.plural !== undefined ? [symbolSpec.plural] : []),
|
|
262
|
+
];
|
|
263
|
+
for (const symbol of symbols) {
|
|
264
|
+
if (metaSymbols.some(meta => meta === symbol))
|
|
265
|
+
throw new Error(`"${symbol}" is a reserved symbol and cannot name a unit`);
|
|
266
|
+
|
|
267
|
+
//parsing tokenizes on these characters (and takes a leading "-" for the sign), so a
|
|
268
|
+
// symbol containing them could never round-trip through its own kind. Digits end a
|
|
269
|
+
// value, so no symbol may start with one, and a prefix symbol (which the value follows,
|
|
270
|
+
// as in "$100") may hold none; a postfix one ends at the next space regardless
|
|
271
|
+
const prefix = (symbolSpec.position ?? "postfix") === "prefix";
|
|
272
|
+
if (symbol === "" || /[\s,_./]/u.test(symbol) || symbol.startsWith("-") ||
|
|
273
|
+
/^[0-9]/.test(symbol) || (prefix && /[0-9]/.test(symbol)))
|
|
274
|
+
throw new Error(
|
|
275
|
+
`Invalid unit symbol "${symbol}": symbols must be non-empty and must not ` +
|
|
276
|
+
`contain whitespace, ",", "_", ".", "/", a leading "-" or a leading digit ` +
|
|
277
|
+
`(nor any digit when prefixed)`
|
|
278
|
+
);
|
|
279
|
+
|
|
280
|
+
if (systems[sysName]!.symbols.includes(symbol))
|
|
281
|
+
throw new Error(`Duplicate symbol "${symbol}" in system ${sysName}`);
|
|
282
|
+
|
|
283
|
+
const existing = allUnits.find(([s]) => symbolKey(s) === symbolKey(symbol));
|
|
284
|
+
if (existing && existing[0] !== symbol)
|
|
285
|
+
throw new Error(`Symbol "${symbol}" is equivalent to "${existing[0]}" (NFKC)`);
|
|
286
|
+
|
|
287
|
+
systems[sysName]!.symbols.push(symbol);
|
|
288
|
+
if (!existing)
|
|
289
|
+
allUnits.push([symbol, unitData as Unit]);
|
|
290
|
+
else if (!existing[1].scale.eq(unitData.scale))
|
|
291
|
+
throw new Error(`Symbol "${symbol}" has conflicting scales across systems`);
|
|
292
|
+
else
|
|
293
|
+
existing[1] = { ...unitData, ...existing[1] } as Unit;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
//descending by scale: a promise to consumers walking `units`, which the type states too
|
|
300
|
+
allUnits.sort((a, b) => b[1].scale.gt(a[1].scale) ? 1 : b[1].scale.lt(a[1].scale) ? -1 : 0);
|
|
301
|
+
const units = fromEntries(allUnits);
|
|
302
|
+
|
|
303
|
+
return { name, units, standard, systems, ...meta } as any;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export function identifyKind<const K extends Kind, A extends boolean = false>(
|
|
307
|
+
kinds: RoArray<K>,
|
|
308
|
+
str: string,
|
|
309
|
+
allowAmbiguous?: A
|
|
310
|
+
): A extends true ? K[] : K | undefined {
|
|
311
|
+
const { pairs } = segment(str);
|
|
312
|
+
const symbols = pairs.map(p => str.substring(p.symbol[0], p.symbol[1]));
|
|
313
|
+
|
|
314
|
+
const matches = kinds.filter(k => symbols.every(s => findUnit(k.units, s) !== undefined));
|
|
315
|
+
|
|
316
|
+
return (allowAmbiguous ? matches : matches.length === 1 ? matches[0] : undefined) as any;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ---- Implementation details ----
|
|
320
|
+
|
|
321
|
+
type UnitBaseSpec = { readonly symbols: RoNeTuple<SymbolSpec> };
|
|
322
|
+
|
|
323
|
+
type UnitSpec<F extends boolean, D extends boolean> =
|
|
324
|
+
UnitBaseSpec & (
|
|
325
|
+
F extends true
|
|
326
|
+
? D extends true
|
|
327
|
+
? Partial<DecimalSpec<0>>
|
|
328
|
+
: Partial<ScaleSpec<1 | 1n>>
|
|
329
|
+
: D extends true ? DecimalSpec : ScaleSpec
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
type KindUnitsSpec<F extends boolean, D extends boolean> =
|
|
333
|
+
D extends any
|
|
334
|
+
? F extends true
|
|
335
|
+
? readonly [UnitSpec<true, D>, ...UnitSpec<false, D>[]]
|
|
336
|
+
: RoNeTuple<UnitSpec<false, D>>
|
|
337
|
+
: never;
|
|
338
|
+
|
|
339
|
+
type KindUnits<U extends string> = { readonly [K in U]?: Unit };
|
|
340
|
+
|
|
341
|
+
type KindSystems<Y extends SystemInfo> = {
|
|
342
|
+
readonly [Sys in Y as Sys["name"]]: Readonly<{
|
|
343
|
+
symbols: RoArray<Sys["symbols"]>; //guaranteed to be in descending order by scale
|
|
344
|
+
decimal: Sys["decimal"];
|
|
345
|
+
}>;
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
type UnitSymbolsOf<U extends RoArray<UnitBaseSpec>> =
|
|
349
|
+
U[number]["symbols"][number] extends infer S
|
|
350
|
+
? S extends SymbolSpec
|
|
351
|
+
? S["symbol"] | Extract<S["plural"], string>
|
|
352
|
+
: never
|
|
353
|
+
: never;
|
|
354
|
+
|
|
355
|
+
type SystemEntry<
|
|
356
|
+
F extends boolean = boolean,
|
|
357
|
+
N extends string = string,
|
|
358
|
+
S extends KindUnitsSpec<F, boolean> = KindUnitsSpec<F, boolean>,
|
|
359
|
+
> = RoPair<N, S>;
|
|
360
|
+
|
|
361
|
+
type SystemsSpec = readonly [SystemEntry<true>, ...SystemEntry<false>[]];
|
|
362
|
+
|
|
363
|
+
type KindUnitsInput = KindUnitsSpec<true, boolean> | SystemsSpec;
|
|
364
|
+
|
|
365
|
+
type IsSystemDecimal<Spec extends KindUnitsSpec<boolean, boolean>> =
|
|
366
|
+
Spec[0] extends DecimalSpec
|
|
367
|
+
? true
|
|
368
|
+
: Spec[0] extends ScaleSpec
|
|
369
|
+
? false
|
|
370
|
+
: Spec extends readonly [unknown] | readonly [unknown, DecimalSpec, ...unknown[]]
|
|
371
|
+
? true
|
|
372
|
+
: false;
|
|
373
|
+
|
|
374
|
+
type SystemInfoOf<I extends KindUnitsInput> =
|
|
375
|
+
I extends SystemsSpec
|
|
376
|
+
? { [K in keyof I & `${number}`]:
|
|
377
|
+
I[K] extends SystemEntry<boolean, infer Name, infer Spec>
|
|
378
|
+
? SystemInfo<Name, UnitSymbolsOf<Spec>, IsSystemDecimal<Spec>>
|
|
379
|
+
: never
|
|
380
|
+
}[keyof I & `${number}`]
|
|
381
|
+
: I extends KindUnitsSpec<true, boolean>
|
|
382
|
+
? SystemInfo<"default", UnitSymbolsOf<I>, IsSystemDecimal<I>>
|
|
383
|
+
: never;
|
|
384
|
+
|
|
385
|
+
type UnitsSpecOf<I extends KindUnitsInput> =
|
|
386
|
+
I extends SystemsSpec ? I[number][1] : I;
|
|
387
|
+
|
|
388
|
+
type StandardSystemOf<I extends KindUnitsInput> =
|
|
389
|
+
I extends SystemsSpec ? I[0][0] : "default";
|
|
390
|
+
|
|
391
|
+
type StandardUnitOf<I extends KindUnitsInput> =
|
|
392
|
+
I extends SystemsSpec
|
|
393
|
+
? I[0][1][0]["symbols"][0]["symbol"]
|
|
394
|
+
: I extends KindUnitsSpec<boolean, boolean>
|
|
395
|
+
? I[0]["symbols"][0]["symbol"]
|
|
396
|
+
: never;
|
|
397
|
+
|
|
398
|
+
type StandardInfoOf<I extends KindUnitsInput> =
|
|
399
|
+
StandardInfo<StandardSystemOf<I>, StandardUnitOf<I>> extends infer S
|
|
400
|
+
extends ValidStandardInfo<SystemInfoOf<I>> //tell tsc that this must hold
|
|
401
|
+
? S
|
|
402
|
+
: never;
|
|
403
|
+
|
|
404
|
+
const metaSymbols = ["standard", "human", "atomic"] as const;
|
|
405
|
+
//Amount.toString takes a system name in the position of its mode argument, so a system named
|
|
406
|
+
// like a mode could never be selected
|
|
407
|
+
const displayModes = ["approximate", "exact", "inUnit"] as const;
|
|
408
|
+
type MetaSymbols = typeof metaSymbols[number];
|
|
409
|
+
type DecimalMetaSymbolOf<K extends Kind, M extends MetaSymbols = MetaSymbols> =
|
|
410
|
+
M extends MetaSymbols
|
|
411
|
+
? K extends Brand<unknown, `decimal${Capitalize<M>}`>
|
|
412
|
+
? M
|
|
413
|
+
: never
|
|
414
|
+
: never;
|
|
415
|
+
|
|
416
|
+
type DecimalSymbolsOfSystem<Y extends SystemInfo> =
|
|
417
|
+
Y extends { symbols: infer Syms extends string; decimal: true }
|
|
418
|
+
? Syms
|
|
419
|
+
: never;
|
|
420
|
+
|
|
421
|
+
type DecimalUnitSymbols<K extends Kind> =
|
|
422
|
+
K["systems"][keyof K["systems"]] extends infer Sys
|
|
423
|
+
? Sys extends { symbols: RoArray<infer S extends string>; decimal: true } ? S : never
|
|
424
|
+
: never;
|
|
425
|
+
|
|
426
|
+
type GetDecimalsOpts<K extends Kind> =
|
|
427
|
+
DecimalSymbolsOf<K> extends infer D
|
|
428
|
+
? ("human" extends D ? Opts<{ of: D }> : { readonly of: D }) &
|
|
429
|
+
("atomic" extends D ? Opts<{ in: D }> : { readonly in: D })
|
|
430
|
+
: never;
|
|
431
|
+
|
|
432
|
+
const addScale = (spec: DecimalSpec) =>
|
|
433
|
+
({ oom: spec.oom, scale: Rational.powerOfTen(spec.oom) });
|
|
434
|
+
|
|
435
|
+
const ensureScale = (decimal: boolean, spec: UnitBaseSpec) =>
|
|
436
|
+
"oom" in spec
|
|
437
|
+
? addScale(spec as DecimalSpec)
|
|
438
|
+
: "scale" in spec
|
|
439
|
+
? { scale: Rational.from((spec as ScaleSpec).scale) } as const
|
|
440
|
+
: decimal
|
|
441
|
+
? { oom: 0, scale: Rational.from(1n) } as const
|
|
442
|
+
: { scale: Rational.from(1n) } as const;
|