@c9up/atom 0.1.10 → 0.1.12

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/src/Decimal.ts CHANGED
@@ -43,9 +43,16 @@ export interface BetweenOptions {
43
43
  inclusive?: boolean;
44
44
  }
45
45
 
46
+ /**
47
+ * `quantize` takes a rounding mode and nothing else.
48
+ *
49
+ * It used to accept a `precision` too, which it validated and then ignored:
50
+ * `quantize('0.01', { precision: 4 })` answered `1.23`, the same as without it.
51
+ * There is nothing the option could have meant — the result's scale comes from
52
+ * the step, which is the whole point of quantizing to one.
53
+ */
46
54
  export interface QuantizeOptions {
47
55
  mode?: RoundMode;
48
- precision?: number;
49
56
  }
50
57
 
51
58
  export interface MedianOptions {
@@ -139,9 +146,13 @@ export class Decimal {
139
146
  localesOrRosetta?: Intl.LocalesArgument | RosettaLike,
140
147
  ): Decimal {
141
148
  if (isRosettaLike(localesOrRosetta)) {
142
- return new Decimal(normalizeViaRosetta(value, localesOrRosetta));
149
+ return new Decimal(
150
+ normalizeLocalized(value, shapeFromRosetta(localesOrRosetta)),
151
+ );
143
152
  }
144
- return new Decimal(normalizeLocaleNumber(value, localesOrRosetta));
153
+ return new Decimal(
154
+ normalizeLocalized(value, shapeFromIntl(localesOrRosetta)),
155
+ );
145
156
  }
146
157
 
147
158
  plus(other: DecimalInput): Decimal {
@@ -332,9 +343,6 @@ export class Decimal {
332
343
  if (!stepValue.gt(0)) {
333
344
  throw new Error("Quantize step must be greater than zero");
334
345
  }
335
- if (options.precision !== undefined) {
336
- assertScale(options.precision);
337
- }
338
346
  const thisParts = parseDecimal(this.#value);
339
347
  const stepParts = parseDecimal(stepValue.#value);
340
348
  const numerator = thisParts.int * pow10BigInt(stepParts.scale);
@@ -466,8 +474,7 @@ export class Decimal {
466
474
  const remainders: Array<{ index: number; remainder: bigint }> = [];
467
475
  let consumed = 0n;
468
476
 
469
- for (let index = 0; index < normalized.length; index++) {
470
- const ratio = normalized[index];
477
+ for (const [index, ratio] of normalized.entries()) {
471
478
  const weighted = total * ratio;
472
479
  const share = weighted / ratioTotal;
473
480
  const remainder = weighted % ratioTotal;
@@ -485,7 +492,13 @@ export class Decimal {
485
492
 
486
493
  let pointer = 0;
487
494
  while (left > 0n) {
488
- baseShares[remainders[pointer].index] += 1n;
495
+ // `remainders` has one entry per ratio and the pointer wraps inside
496
+ // it, so both reads land; naming them is what says so.
497
+ const target = remainders[pointer];
498
+ if (target === undefined) break;
499
+ const share = baseShares[target.index];
500
+ if (share === undefined) break;
501
+ baseShares[target.index] = share + 1n;
489
502
  left -= 1n;
490
503
  pointer++;
491
504
  if (pointer >= remainders.length) pointer = 0;
@@ -505,10 +518,29 @@ export class Decimal {
505
518
  };
506
519
  }
507
520
 
521
+ /**
522
+ * The canonical decimal — trailing zeros trimmed. `'001.2300'` prints
523
+ * `'1.23'`, and `'100.0000000000'` prints `'100'`.
524
+ *
525
+ * **The scale belongs to your domain, not to the value.** To persist or to
526
+ * allocate, pass the scale your column declares — `toFixed(10)`,
527
+ * `toScale(10)` — never the one the value happens to be carrying. A scale
528
+ * read back off a value is not a promise:
529
+ *
530
+ * new Decimal('100.0000000000').toParts().scale // 10
531
+ * new Decimal('100.0000000000').plus('0').toParts() // scale 0
532
+ *
533
+ * Arithmetic does not carry a declared scale, so anything that has been
534
+ * through `plus` or `times` has whatever scale its VALUE needs. That
535
+ * matters because `allocate()` splits at the scale it finds: the same
536
+ * amount divides into `33 + 67` or `33.3333333333 + 66.6666666667`
537
+ * depending on a property this method deliberately does not show you.
538
+ */
508
539
  toString(): string {
509
540
  return this.#value;
510
541
  }
511
542
 
543
+ /** The canonical form — see {@link toString} on storing a scale. */
512
544
  toJSON(): string {
513
545
  return this.#value;
514
546
  }
@@ -552,10 +584,7 @@ export class Decimal {
552
584
  if (isRosettaLike(localesOrRosetta)) {
553
585
  return localesOrRosetta.formatNumberString(this.#value, options);
554
586
  }
555
- const formatter = new Intl.NumberFormat(
556
- localesOrRosetta,
557
- options,
558
- ) as StringFormatter;
587
+ const formatter = stringFormatter(localesOrRosetta, options);
559
588
  if (this.isInteger()) {
560
589
  return formatter.format(BigInt(this.#value));
561
590
  }
@@ -574,6 +603,20 @@ interface StringFormatter extends Intl.NumberFormat {
574
603
  format(value: number | bigint | string): string;
575
604
  }
576
605
 
606
+ /**
607
+ * A formatter typed with that overload.
608
+ *
609
+ * A method parameter is compared bivariantly, so the constructed formatter
610
+ * satisfies the wider signature on its own — the assertion that used to sit
611
+ * here was claiming something the compiler could check.
612
+ */
613
+ function stringFormatter(
614
+ locales?: Intl.LocalesArgument,
615
+ options?: Intl.NumberFormatOptions,
616
+ ): StringFormatter {
617
+ return new Intl.NumberFormat(locales, options);
618
+ }
619
+
577
620
  function normalizeInput(value: DecimalInput): string {
578
621
  if (value instanceof Decimal) {
579
622
  return value.toString();
@@ -623,7 +666,7 @@ function decimalStringFromNumber(value: number): string {
623
666
 
624
667
  const [coefficient, exponentRaw] = raw.toLowerCase().split("e");
625
668
  const exponent = Number(exponentRaw);
626
- if (!Number.isInteger(exponent)) {
669
+ if (coefficient === undefined || !Number.isInteger(exponent)) {
627
670
  throw new Error(`Invalid decimal: ${value}`);
628
671
  }
629
672
  const negative = coefficient.startsWith("-");
@@ -631,7 +674,7 @@ function decimalStringFromNumber(value: number): string {
631
674
  negative || coefficient.startsWith("+")
632
675
  ? coefficient.slice(1)
633
676
  : coefficient;
634
- const [wholeRaw, fracRaw = ""] = unsigned.split(".");
677
+ const [wholeRaw = "", fracRaw = ""] = unsigned.split(".");
635
678
  const digits = `${wholeRaw}${fracRaw}`;
636
679
  const decimalIndex = wholeRaw.length + exponent;
637
680
 
@@ -847,85 +890,177 @@ function isRosettaLike(arg: unknown): arg is RosettaLike {
847
890
  typeof arg === "object" &&
848
891
  arg !== null &&
849
892
  "getNumberFormatData" in arg &&
850
- typeof (arg as RosettaLike).getNumberFormatData === "function" &&
893
+ typeof arg.getNumberFormatData === "function" &&
851
894
  "formatNumberString" in arg &&
852
- typeof (arg as RosettaLike).formatNumberString === "function"
895
+ typeof arg.formatNumberString === "function"
853
896
  );
854
897
  }
855
898
 
856
- function normalizeViaRosetta(input: string, rosetta: RosettaLike): string {
857
- const trimmed = input.trim();
858
- if (!trimmed) {
859
- throw new Error("Invalid localized decimal: empty string");
860
- }
899
+ /**
900
+ * Everything reading one locale's numbers takes.
901
+ *
902
+ * There is one normalizer, and both ways of naming a locale build one of
903
+ * these for it. Written as two normalizers, the Rosetta one carried a subset
904
+ * of the rules: it substituted the separators but never mapped the locale's
905
+ * digits and validated grouping against the 3-3 of en-US whatever the locale
906
+ * actually grouped by. `parseLocale('١٬٢٣٤٫٥٦', 'ar-EG')` and
907
+ * `parseLocale('١٬٢٣٤٫٥٦', rosetta)` therefore disagreed about the same locale
908
+ * — the second refused it — and so did every Indic locale, whose 3-2 grouping
909
+ * only the first one asked about.
910
+ */
911
+ interface LocaleNumberShape {
912
+ decimal: string;
913
+ group: string;
914
+ minus: string;
915
+ plusSign: string;
916
+ /** The locale's digits, mapped back to ASCII. Empty when they are ASCII. */
917
+ digits: ReadonlyMap<string, string>;
918
+ grouping: GroupingSpec;
919
+ }
920
+
921
+ interface GroupingSpec {
922
+ primary: number;
923
+ secondary: number;
924
+ }
925
+
926
+ /** Format a decimal string the way one locale writes it. */
927
+ type FormatNumber = (
928
+ value: string,
929
+ options?: Intl.NumberFormatOptions,
930
+ ) => string;
931
+
932
+ function shapeFromIntl(locales?: Intl.LocalesArgument): LocaleNumberShape {
933
+ const format: FormatNumber = (value, options) =>
934
+ stringFormatter(locales, options).format(value);
935
+ const parts = new Intl.NumberFormat(locales).formatToParts(-12345.6);
936
+ const signed = new Intl.NumberFormat(locales, {
937
+ signDisplay: "always",
938
+ }).formatToParts(1);
939
+ const group = parts.find((part) => part.type === "group")?.value ?? ",";
940
+ const digits = digitsOf(format);
941
+ return {
942
+ decimal: parts.find((part) => part.type === "decimal")?.value ?? ".",
943
+ group,
944
+ minus: parts.find((part) => part.type === "minusSign")?.value ?? "-",
945
+ plusSign: signed.find((part) => part.type === "plusSign")?.value ?? "+",
946
+ digits,
947
+ grouping: groupingOf(format, group, digits),
948
+ };
949
+ }
950
+
951
+ /**
952
+ * The same shape, read off a Rosetta.
953
+ *
954
+ * `getNumberFormatData` names the separators; the digits and the grouping come
955
+ * out of `formatNumberString`, which the same interface already declares — so
956
+ * the Rosetta path honours its fallback chain for all four without asking the
957
+ * caller for a locale tag it deliberately does not take.
958
+ */
959
+ function shapeFromRosetta(rosetta: RosettaLike): LocaleNumberShape {
960
+ const format: FormatNumber = (value, options) =>
961
+ rosetta.formatNumberString(value, options);
861
962
  const raw = rosetta.getNumberFormatData();
862
- // Guard: a malformed `RosettaLike` returning empty separators
863
- // would produce regexes matching every position. Fall back to
864
- // ASCII defaults rather than corrupting the input.
865
- const data = {
963
+ // A malformed `RosettaLike` returning empty separators would build regexes
964
+ // matching every position. Fall back to ASCII rather than corrupt the input.
965
+ const group = raw.group || ",";
966
+ const digits = digitsOf(format);
967
+ return {
866
968
  decimal: raw.decimal || ".",
867
- group: raw.group || ",",
969
+ group,
868
970
  minus: raw.minus || "-",
869
971
  plusSign: raw.plusSign || "+",
972
+ digits,
973
+ grouping: groupingOf(format, group, digits),
870
974
  };
871
- let normalized = trimmed.replace(/\s| | /g, "");
872
- if (data.minus !== "-") {
873
- normalized = normalized.replace(
874
- new RegExp(escapeRegExp(data.minus), "g"),
875
- "-",
876
- );
877
- }
878
- normalized = normalized.replace(/[−﹣-]/g, "-");
879
- // Substitute the locale's plus sign (e.g., U+FF0B `+`,
880
- // U+FB29 `﬩`) to ASCII so the strict validator accepts it.
881
- if (data.plusSign !== "+") {
882
- normalized = normalized.replace(
883
- new RegExp(escapeRegExp(data.plusSign), "g"),
884
- "+",
885
- );
886
- }
975
+ }
887
976
 
888
- if (/^\(.*\)$/.test(normalized)) {
889
- normalized = `-${normalized.slice(1, -1)}`;
977
+ /**
978
+ * The locale's ten digits, read off the formatter rather than a table.
979
+ *
980
+ * One call names all ten in a known order, so a numbering system whose code
981
+ * points are not contiguous — or one the runtime added after this was written
982
+ * — needs nothing here.
983
+ */
984
+ function digitsOf(format: FormatNumber): ReadonlyMap<string, string> {
985
+ const map = new Map<string, string>();
986
+ let formatted: string;
987
+ try {
988
+ formatted = format("9876543210", { useGrouping: false });
989
+ } catch {
990
+ return map;
890
991
  }
891
-
892
- validateLocalizedSyntax(normalized, data.group, data.decimal);
893
- normalized = normalized.replace(
894
- new RegExp(escapeRegExp(data.group), "g"),
895
- "",
896
- );
897
- normalized = normalized.replace(
898
- new RegExp(escapeRegExp(data.decimal), "g"),
899
- ".",
900
- );
901
-
902
- if (!/^[+-]?\d+(\.\d+)?$/.test(normalized)) {
903
- throw new Error(`Invalid localized decimal: ${input}`);
992
+ let value = 9;
993
+ for (const char of formatted) {
994
+ if (!map.has(char) && value >= 0) {
995
+ map.set(char, String(value));
996
+ value--;
997
+ }
904
998
  }
905
- return normalized;
999
+ return map;
1000
+ }
1001
+
1002
+ /**
1003
+ * How wide the locale's groups are — 3-3 for `1,234,567`, 3-2 for the Indic
1004
+ * `12,34,567`. Measured on a number long enough to show both.
1005
+ */
1006
+ function groupingOf(
1007
+ format: FormatNumber,
1008
+ group: string,
1009
+ digits: ReadonlyMap<string, string>,
1010
+ ): GroupingSpec {
1011
+ const fallback: GroupingSpec = { primary: 3, secondary: 3 };
1012
+ let formatted: string;
1013
+ try {
1014
+ formatted = format("1234567890123");
1015
+ } catch {
1016
+ return fallback;
1017
+ }
1018
+ const segments = toAsciiDigits(formatted, digits).split(group);
1019
+ if (segments.length < 2) return fallback;
1020
+ // Code points, not UTF-16 units: a digit outside the BMP counts once.
1021
+ const lengths = segments.map((segment) => [...segment].length);
1022
+ const primary = lengths.at(-1) ?? 3;
1023
+ const secondary = lengths.at(-2) ?? primary;
1024
+ return { primary, secondary };
906
1025
  }
907
1026
 
908
- function normalizeLocaleNumber(
1027
+ function toAsciiDigits(
909
1028
  input: string,
910
- locales?: Intl.LocalesArgument,
1029
+ digits: ReadonlyMap<string, string>,
911
1030
  ): string {
1031
+ if (digits.size === 0) return input;
1032
+ let out = "";
1033
+ for (const char of input) {
1034
+ out += digits.get(char) ?? char;
1035
+ }
1036
+ return out;
1037
+ }
1038
+
1039
+ function normalizeLocalized(input: string, shape: LocaleNumberShape): string {
912
1040
  const trimmed = input.trim();
913
1041
  if (!trimmed) {
914
1042
  throw new Error("Invalid localized decimal: empty string");
915
1043
  }
916
1044
 
917
- const formatter = new Intl.NumberFormat(locales);
918
- const parts = formatter.formatToParts(-12345.6);
919
- const group = parts.find((part) => part.type === "group")?.value ?? ",";
920
- const decimal = parts.find((part) => part.type === "decimal")?.value ?? ".";
921
- const minus = parts.find((part) => part.type === "minusSign")?.value ?? "-";
922
-
923
- let normalized = normalizeLocaleDigits(trimmed, locales);
924
- normalized = normalized.replace(/\s|\u00A0|\u202F/g, "");
925
- if (minus !== "-") {
926
- normalized = normalized.replace(new RegExp(escapeRegExp(minus), "g"), "-");
1045
+ let normalized = toAsciiDigits(trimmed, shape.digits);
1046
+ // `\s` already covers U+00A0 and U+202F, which is what the space-grouping
1047
+ // locales separate with.
1048
+ normalized = normalized.replace(/\s/g, "");
1049
+ if (shape.minus !== "-") {
1050
+ normalized = normalized.replace(
1051
+ new RegExp(escapeRegExp(shape.minus), "g"),
1052
+ "-",
1053
+ );
927
1054
  }
928
1055
  normalized = normalized.replace(/[−﹣-]/g, "-");
1056
+ // The locale's plus sign (e.g. U+FF0B `+`, U+FB29 `﬩`) becomes ASCII so the
1057
+ // strict validator below accepts it.
1058
+ if (shape.plusSign !== "+") {
1059
+ normalized = normalized.replace(
1060
+ new RegExp(escapeRegExp(shape.plusSign), "g"),
1061
+ "+",
1062
+ );
1063
+ }
929
1064
 
930
1065
  if (/^\(.*\)$/.test(normalized)) {
931
1066
  normalized = `-${normalized.slice(1, -1)}`;
@@ -933,12 +1068,18 @@ function normalizeLocaleNumber(
933
1068
 
934
1069
  validateLocalizedSyntax(
935
1070
  normalized,
936
- group,
937
- decimal,
938
- getLocaleGrouping(locales),
1071
+ shape.group,
1072
+ shape.decimal,
1073
+ shape.grouping,
1074
+ );
1075
+ normalized = normalized.replace(
1076
+ new RegExp(escapeRegExp(shape.group), "g"),
1077
+ "",
1078
+ );
1079
+ normalized = normalized.replace(
1080
+ new RegExp(escapeRegExp(shape.decimal), "g"),
1081
+ ".",
939
1082
  );
940
- normalized = normalized.replace(new RegExp(escapeRegExp(group), "g"), "");
941
- normalized = normalized.replace(new RegExp(escapeRegExp(decimal), "g"), ".");
942
1083
 
943
1084
  if (!/^[+-]?\d+(\.\d+)?$/.test(normalized)) {
944
1085
  throw new Error(`Invalid localized decimal: ${input}`);
@@ -946,62 +1087,6 @@ function normalizeLocaleNumber(
946
1087
  return normalized;
947
1088
  }
948
1089
 
949
- function normalizeLocaleDigits(
950
- input: string,
951
- locales?: Intl.LocalesArgument,
952
- ): string {
953
- const digitMap = getLocaleDigitMap(locales);
954
- let out = "";
955
- for (const char of input) {
956
- out += digitMap.get(char) ?? char;
957
- }
958
- return out;
959
- }
960
-
961
- function getLocaleDigitMap(
962
- locales?: Intl.LocalesArgument,
963
- ): Map<string, string> {
964
- const formatter = new Intl.NumberFormat(locales, { useGrouping: false });
965
- const digits = formatter.format(9876543210);
966
- const map = new Map<string, string>();
967
- let value = 9;
968
- for (const char of digits) {
969
- if (!map.has(char) && value >= 0) {
970
- map.set(char, String(value));
971
- value--;
972
- }
973
- }
974
- return map;
975
- }
976
-
977
- interface GroupingSpec {
978
- primary: number;
979
- secondary: number;
980
- }
981
-
982
- function getLocaleGrouping(locales?: Intl.LocalesArgument): GroupingSpec {
983
- const parts = new Intl.NumberFormat(locales)
984
- .formatToParts(1234567890123)
985
- .filter((part) => part.type === "integer" || part.type === "group");
986
- const lengths: number[] = [];
987
- let current = 0;
988
- for (const part of parts) {
989
- if (part.type === "integer") {
990
- current += [...part.value].length;
991
- } else {
992
- lengths.push(current);
993
- current = 0;
994
- }
995
- }
996
- lengths.push(current);
997
- if (lengths.length < 2) {
998
- return { primary: 3, secondary: 3 };
999
- }
1000
- const primary = lengths[lengths.length - 1];
1001
- const secondary = lengths[lengths.length - 2] ?? primary;
1002
- return { primary, secondary };
1003
- }
1004
-
1005
1090
  function validateLocalizedSyntax(
1006
1091
  value: string,
1007
1092
  group: string,
@@ -1035,11 +1120,14 @@ function validateLocalizedSyntax(
1035
1120
  }
1036
1121
  for (let i = groups.length - 1, distance = 0; i >= 0; i--, distance++) {
1037
1122
  const size = distance === 0 ? grouping.primary : grouping.secondary;
1123
+ const group = groups[i];
1124
+ if (group === undefined)
1125
+ throw new Error(`Invalid localized decimal: ${value}`);
1038
1126
  if (i === 0) {
1039
- if (groups[i].length < 1 || groups[i].length > size) {
1127
+ if (group.length < 1 || group.length > size) {
1040
1128
  throw new Error(`Invalid localized decimal: ${value}`);
1041
1129
  }
1042
- } else if (groups[i].length !== size) {
1130
+ } else if (group.length !== size) {
1043
1131
  throw new Error(`Invalid localized decimal: ${value}`);
1044
1132
  }
1045
1133
  }
package/src/Money.ts CHANGED
@@ -7,18 +7,53 @@ export interface MoneyOptions {
7
7
  mode?: RoundMode;
8
8
  }
9
9
 
10
+ /**
11
+ * What `times` and `div` take.
12
+ *
13
+ * The rounding mode, and only that. They used to declare the whole
14
+ * {@link MoneyOptions} and forward one field of it: `scale` and `exact` were
15
+ * overwritten on the way through, so `.times("1.5", { scale: 4 })` came back at
16
+ * the currency's scale and `.times("1.333", { exact: true })` rounded anyway.
17
+ * Neither could ever have been honoured — a multiplied amount of money is still
18
+ * money, and money is held at the scale its currency has.
19
+ */
20
+ export interface MoneyRoundingOptions {
21
+ mode?: RoundMode;
22
+ }
23
+
10
24
  export interface MoneyFormatOptions
11
25
  extends Omit<Intl.NumberFormatOptions, "style" | "currency"> {
12
26
  locale?: Intl.LocalesArgument;
13
27
  }
14
28
 
29
+ /**
30
+ * ISO 4217 minor units, for every currency that does not have two.
31
+ *
32
+ * Only the exceptions are listed; anything absent is two, which is what the
33
+ * lookups below fall back to. Written as a partial list of the ones someone
34
+ * happened to think of, the fallback answered for the rest — and answered
35
+ * wrong. A `Money` built for ISK, whose króna has no subunit, was held at two
36
+ * decimals: `toMinorUnits()` returned 123400 for 1234 króna, a hundredfold
37
+ * error on its way into an integer column, `toString()` printed a fractional
38
+ * part the currency does not have, and `format()` asked Intl for two decimals
39
+ * it would otherwise have refused to print. JPY, three lines away in the same
40
+ * table, was correct — the difference between the two was which one someone
41
+ * had typed in.
42
+ *
43
+ * ISO is the source of truth here, not CLDR, and they disagree on one entry:
44
+ * ISO gives IQD three minor units, `Intl` prints zero. `format()` passes this
45
+ * scale to Intl explicitly, so the value keeps the fils ISO says it has.
46
+ */
15
47
  const ISO_MINOR_UNITS: Record<string, number> = Object.freeze({
16
48
  BHD: 3,
49
+ BIF: 0,
17
50
  CLF: 4,
18
51
  CLP: 0,
19
52
  DJF: 0,
20
- EUR: 2,
21
- GBP: 2,
53
+ GNF: 0,
54
+ IQD: 3,
55
+ ISK: 0,
56
+ JOD: 3,
22
57
  JPY: 0,
23
58
  KMF: 0,
24
59
  KRW: 0,
@@ -26,9 +61,13 @@ const ISO_MINOR_UNITS: Record<string, number> = Object.freeze({
26
61
  LYD: 3,
27
62
  OMR: 3,
28
63
  PYG: 0,
64
+ RWF: 0,
29
65
  TND: 3,
30
- USD: 2,
66
+ UGX: 0,
67
+ UYI: 0,
68
+ UYW: 4,
31
69
  VND: 0,
70
+ VUV: 0,
32
71
  XAF: 0,
33
72
  XOF: 0,
34
73
  XPF: 0,
@@ -102,7 +141,7 @@ export class Money {
102
141
  });
103
142
  }
104
143
 
105
- times(multiplier: DecimalInput, options: MoneyOptions = {}): Money {
144
+ times(multiplier: DecimalInput, options: MoneyRoundingOptions = {}): Money {
106
145
  return new Money(this.#amount.times(multiplier), this.#currency, {
107
146
  scale: this.#scale,
108
147
  exact: false,
@@ -110,7 +149,7 @@ export class Money {
110
149
  });
111
150
  }
112
151
 
113
- div(divisor: DecimalInput, options: MoneyOptions = {}): Money {
152
+ div(divisor: DecimalInput, options: MoneyRoundingOptions = {}): Money {
114
153
  return new Money(this.#amount.div(divisor), this.#currency, {
115
154
  scale: this.#scale,
116
155
  exact: false,
package/src/atlas.ts CHANGED
@@ -5,9 +5,8 @@
5
5
  * column pipeline. Lives on a sub-export so the default `@c9up/atom` import
6
6
  * surface stays adapter-free.
7
7
  *
8
- * Mirrors Adonis Lucid's `@column.prepare` / `@column.consume` pattern
9
- * callbacks are baked into the entity definition; no global registry, no
10
- * boot-time wiring.
8
+ * The callbacks are baked into the entity definition: no global registry, no
9
+ * boot-time wiring, nothing to import in a provider.
11
10
  *
12
11
  * Usage:
13
12
  *
@@ -19,8 +18,6 @@
19
18
  * @PrimaryKey() id!: number
20
19
  * @Column(decimalAtlasAdapter) balance!: Decimal | null
21
20
  * }
22
- *
23
- * @implements Story 35.10
24
21
  */
25
22
 
26
23
  import { Decimal, type RoundMode } from "./Decimal.js";