@fulcro/types 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +66 -0
  3. package/dist/bigInteger/index.d.ts +27 -0
  4. package/dist/bigInteger/index.js +76 -0
  5. package/dist/brand/index.d.ts +25 -0
  6. package/dist/brand/index.js +2 -0
  7. package/dist/decimal/arithmetic.d.ts +99 -0
  8. package/dist/decimal/arithmetic.js +388 -0
  9. package/dist/decimal/format.d.ts +55 -0
  10. package/dist/decimal/format.js +195 -0
  11. package/dist/decimal/index.d.ts +296 -0
  12. package/dist/decimal/index.js +407 -0
  13. package/dist/decimal/parse.d.ts +15 -0
  14. package/dist/decimal/parse.js +80 -0
  15. package/dist/decimal/parts.d.ts +83 -0
  16. package/dist/decimal/parts.js +103 -0
  17. package/dist/decimal/round.d.ts +32 -0
  18. package/dist/decimal/round.js +132 -0
  19. package/dist/doublePrecisionFloat/index.d.ts +19 -0
  20. package/dist/doublePrecisionFloat/index.js +12 -0
  21. package/dist/float/index.d.ts +25 -0
  22. package/dist/float/index.js +61 -0
  23. package/dist/halfPrecisionFloat/index.d.ts +20 -0
  24. package/dist/halfPrecisionFloat/index.js +63 -0
  25. package/dist/index.d.ts +22 -0
  26. package/dist/index.js +29 -0
  27. package/dist/integer/index.d.ts +163 -0
  28. package/dist/integer/index.js +402 -0
  29. package/dist/languageService/index.d.ts +21 -0
  30. package/dist/languageService/index.js +23 -0
  31. package/dist/layout/index.d.ts +33 -0
  32. package/dist/layout/index.js +2 -0
  33. package/dist/numericType/index.d.ts +174 -0
  34. package/dist/numericType/index.js +2 -0
  35. package/dist/roundingMode/index.d.ts +30 -0
  36. package/dist/roundingMode/index.js +29 -0
  37. package/dist/signedInteger/index.d.ts +40 -0
  38. package/dist/signedInteger/index.js +34 -0
  39. package/dist/singlePrecisionFloat/index.d.ts +20 -0
  40. package/dist/singlePrecisionFloat/index.js +15 -0
  41. package/dist/transformer/classify/index.d.ts +39 -0
  42. package/dist/transformer/classify/index.js +84 -0
  43. package/dist/transformer/index.d.ts +27 -0
  44. package/dist/transformer/index.js +30 -0
  45. package/dist/transformer/rewriter/index.d.ts +3 -0
  46. package/dist/transformer/rewriter/index.js +374 -0
  47. package/dist/unplugin/index.d.mts +15 -0
  48. package/dist/unplugin/index.mjs +31 -0
  49. package/dist/unsignedInteger/index.d.ts +36 -0
  50. package/dist/unsignedInteger/index.js +34 -0
  51. package/package.json +71 -0
package/LICENSE ADDED
@@ -0,0 +1,15 @@
1
+ ISC License
2
+
3
+ Copyright (c) 2026 diguu <rodrigogeribola@hotmail.com>
4
+
5
+ Permission to use, copy, modify, and/or distribute this software for any
6
+ purpose with or without fee is hereby granted, provided that the above
7
+ copyright notice and this permission notice appear in all copies.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
10
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
11
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
12
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
13
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
14
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
15
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,66 @@
1
+ # @fulcro/types
2
+
3
+ Numeric types with a declared range and layout: fixed-width integers, half,
4
+ single and double precision floats, an integer of any size, and a `Decimal` with
5
+ the semantics of IEEE 754 decimal128 — with the JavaScript operators working on
6
+ every one of them once the plugin is wired up.
7
+
8
+ ```sh
9
+ npm install @fulcro/types
10
+ ```
11
+
12
+ ```ts
13
+ import { Decimal, SignedInteger, UnsignedInteger } from '@fulcro/types';
14
+
15
+ const Int32 = SignedInteger(32);
16
+
17
+ Int32.add(Int32.from(2), Int32.from(3)); // 5
18
+ Int32.add(Int32.maximum, Int32.from(1)); // RangeError: outside [-2147483648, 2147483647]
19
+ UnsignedInteger(8).wrap(-1); // 255, when modular arithmetic is what you mean
20
+
21
+ Decimal.from('0.1').add(Decimal.from('0.2')).toString(); // '0.3'
22
+ Decimal.from('19.99').multiply(Decimal.from(3)).toString(); // '59.97'
23
+ ```
24
+
25
+ | Type | What it is |
26
+ | -------------------------------------------- | -------------------------------------------------- |
27
+ | `SignedInteger<N>`, `UnsignedInteger<N>` | 8 to 128 bits, checked arithmetic, `wrap` |
28
+ | `HalfPrecisionFloat`, `SinglePrecisionFloat` | binary16 and binary32, each operation rounded once |
29
+ | `DoublePrecisionFloat` | binary64, the `number` named as a choice |
30
+ | `BigInteger` | an integer of any size |
31
+ | `Decimal` | 34 decimal digits, five rounding modes |
32
+
33
+ Each type has a value of the same name that converts, recognises and computes,
34
+ and every type but `BigInteger` reports its `minimum` and `maximum`. If you only
35
+ need the types, `import type` them and no code is loaded.
36
+
37
+ ## Operators
38
+
39
+ With the plugin, the operators mean what the type means — checked on an
40
+ integer, rounded on a float, decimal128 on a `Decimal` — and the result keeps
41
+ its type. Both operands have to be the same type, or it is a type error at the
42
+ line:
43
+
44
+ ```ts
45
+ const total = price * Decimal.from(3); // Decimal
46
+ count++; // SignedInteger<32>.increment, checked
47
+ a + 1; // type error: 'number' is not 'SignedInteger<32>'
48
+ ```
49
+
50
+ | Tool | Wire in |
51
+ | ---------- | ---------------------------------------------------------------------------------------- |
52
+ | `tsc` | `{ "transform": "@fulcro/types/transformer", "transformProgram": true }`, via `ts-patch` |
53
+ | A bundler | `vite` (or `rollup`, `webpack`, `esbuild`, …) from `@fulcro/types/unplugin` |
54
+ | The editor | `{ "name": "@fulcro/types/language-service" }` in the tsconfig `plugins` |
55
+
56
+ Without it, the operators are the language's own: unchecked on the
57
+ `number`-backed types, refused on a `Decimal`.
58
+
59
+ Every type but `BigInteger` declares a size and alignment, which
60
+ `sizeOf<T>()` and `alignOf<T>()` from `@fulcro/reflect` read at compile time.
61
+
62
+ ---
63
+
64
+ **Full guide:** [docs/types.md](../../docs/types.md) — ranges, rounding, the
65
+ operators, the special values and the layout table.
66
+ 🇧🇷 [Leia em português](../../docs/pt-BR/types.md).
@@ -0,0 +1,27 @@
1
+ import type { Branded } from '../brand/index.js';
2
+ import type { NumericType } from '../numericType/index.js';
3
+ /**
4
+ * An integer of any size.
5
+ *
6
+ * Carried by a `bigint`, which is exact at every magnitude, and branded like
7
+ * every other numeric type here: a `BigInteger` is a `bigint` that went through
8
+ * {@link BigInteger.from}, not any `bigint` at all. That is what lets the
9
+ * operators be rewritten for this type and left alone on every other `bigint`
10
+ * of a program.
11
+ *
12
+ * It is also the one numeric type here with no fixed layout — its size is the
13
+ * size of its value — which is why `sizeOf<BigInteger>()` is a type error.
14
+ */
15
+ export type BigInteger = Branded<bigint, 'BigInteger'>;
16
+ /**
17
+ * The descriptor of {@link BigInteger}.
18
+ *
19
+ * ```ts
20
+ * BigInteger.from('123456789012345678901234567890'); // 123456789012345678901234567890n
21
+ * BigInteger.from(1.5); // RangeError: expected an integer
22
+ * ```
23
+ *
24
+ * Nothing overflows, so the arithmetic throws only on a zero divisor and on a
25
+ * negative exponent.
26
+ */
27
+ export declare const BigInteger: NumericType<BigInteger, number | bigint | string>;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BigInteger = void 0;
4
+ /** Integer literal accepted by {@link BigInteger.from}: decimal digits only. */
5
+ const INTEGER_LITERAL = /^[+-]?\d+$/;
6
+ /**
7
+ * Refuses a zero divisor with the operation named.
8
+ *
9
+ * @param operation Operation being performed.
10
+ * @param divisor Divisor it was handed.
11
+ */
12
+ const requireDivisor = (operation, divisor) => {
13
+ if (divisor === 0n) {
14
+ throw new RangeError(`BigInteger.${operation}: division by zero.`);
15
+ }
16
+ };
17
+ /**
18
+ * The descriptor of {@link BigInteger}.
19
+ *
20
+ * ```ts
21
+ * BigInteger.from('123456789012345678901234567890'); // 123456789012345678901234567890n
22
+ * BigInteger.from(1.5); // RangeError: expected an integer
23
+ * ```
24
+ *
25
+ * Nothing overflows, so the arithmetic throws only on a zero divisor and on a
26
+ * negative exponent.
27
+ */
28
+ exports.BigInteger = {
29
+ name: 'BigInteger',
30
+ from: (value) => {
31
+ if (typeof value === 'bigint')
32
+ return value;
33
+ if (typeof value === 'number') {
34
+ if (!Number.isInteger(value)) {
35
+ throw new RangeError(`BigInteger.from: expected an integer, received ${value}.`);
36
+ }
37
+ return BigInt(value);
38
+ }
39
+ if (typeof value === 'string') {
40
+ // `BigInt` alone would also take `0x1f`, `0b101` and surrounding
41
+ // whitespace. A decimal type accepting a hexadecimal string is a
42
+ // surprise nobody reading `from('0x10')` expects to be 16.
43
+ if (!INTEGER_LITERAL.test(value)) {
44
+ throw new SyntaxError(`BigInteger.from: expected decimal digits with an optional sign, received ${JSON.stringify(value)}.`);
45
+ }
46
+ return BigInt(value);
47
+ }
48
+ throw new TypeError(`BigInteger.from: expected a number, a bigint or a string, received ${typeof value}.`);
49
+ },
50
+ is: (value) => typeof value === 'bigint',
51
+ add: (left, right) => (left + right),
52
+ subtract: (left, right) => (left - right),
53
+ multiply: (left, right) => (left * right),
54
+ divide: (left, right) => {
55
+ requireDivisor('divide', right);
56
+ return (left / right);
57
+ },
58
+ remainder: (left, right) => {
59
+ requireDivisor('remainder', right);
60
+ return (left % right);
61
+ },
62
+ power: (base, exponent) => {
63
+ if (exponent < 0n) {
64
+ throw new RangeError(`BigInteger.power: expected an exponent of zero or more, received ${exponent}n.`);
65
+ }
66
+ return (base ** exponent);
67
+ },
68
+ negate: (value) => -value,
69
+ increment: (value) => (value + 1n),
70
+ decrement: (value) => (value - 1n),
71
+ equals: (left, right) => left === right,
72
+ lessThan: (left, right) => left < right,
73
+ lessThanOrEqual: (left, right) => left <= right,
74
+ greaterThan: (left, right) => left > right,
75
+ greaterThanOrEqual: (left, right) => left >= right,
76
+ };
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Key of the brand, declared and never defined.
3
+ *
4
+ * A `unique symbol` rather than a string so that no consumer can spell the
5
+ * brand by hand: the only way to hold a `SignedInteger<32>` is to be given one
6
+ * by the function that checked it.
7
+ */
8
+ declare const brand: unique symbol;
9
+ /**
10
+ * A primitive tagged, at the type level only, with the name of the numeric type
11
+ * it was checked against.
12
+ *
13
+ * Nothing of this exists at runtime — a branded `number` is a `number` — which is
14
+ * what lets a consumer take only the types of this package and pay for no code.
15
+ * The price is that the brand does not survive arithmetic written with the
16
+ * operators: `a + b` is a plain `number` again, and has to go back through the
17
+ * type's own `add` to be one of ours.
18
+ *
19
+ * @template TBase Primitive that carries the value.
20
+ * @template TName Name of the numeric type the value was checked against.
21
+ */
22
+ export type Branded<TBase, TName extends string> = TBase & {
23
+ readonly [brand]: TName;
24
+ };
25
+ export {};
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,99 @@
1
+ import type { RoundingMode } from '../roundingMode/index.js';
2
+ import { type DecimalParts } from './parts';
3
+ /**
4
+ * The operations of `Decimal`, on parts.
5
+ *
6
+ * Each one computes an exact result on integers and hands it to `finish`, which
7
+ * rounds it into the format once. The care is in keeping "exact" affordable: a
8
+ * decimal128 exponent spans more than twelve thousand powers of ten, and an
9
+ * operation that aligned two operands by multiplying one of them out would
10
+ * build a twelve-thousand-digit integer to add 1 to 10^6000. None of the
11
+ * operations below lets an intermediate grow past about a hundred digits,
12
+ * whatever the exponents, and the performance suite holds them to it.
13
+ */
14
+ /**
15
+ * Adds two values.
16
+ *
17
+ * @param left First operand.
18
+ * @param right Second operand.
19
+ * @param mode Rounding mode.
20
+ * @returns The rounded sum.
21
+ */
22
+ export declare const addParts: (left: DecimalParts, right: DecimalParts, mode: RoundingMode) => DecimalParts;
23
+ /**
24
+ * Multiplies two values.
25
+ *
26
+ * The product of two coefficients has at most sixty-eight digits, and the
27
+ * exponents simply add, so nothing here needs guarding.
28
+ *
29
+ * @param left First operand.
30
+ * @param right Second operand.
31
+ * @param mode Rounding mode.
32
+ * @returns The rounded product.
33
+ */
34
+ export declare const multiplyParts: (left: DecimalParts, right: DecimalParts, mode: RoundingMode) => DecimalParts;
35
+ /**
36
+ * Divides one value by another.
37
+ *
38
+ * The dividend is scaled so that the quotient has thirty-five digits — one
39
+ * more than the format keeps — and a non-zero remainder is recorded as a
40
+ * thirty-sixth digit of 1. Those two digits are all the rounding reads, so the
41
+ * quotient is rounded exactly as the infinitely precise one would be.
42
+ *
43
+ * Division by zero follows IEEE 754 rather than throwing: a non-zero value over
44
+ * zero is an infinity, and zero over zero is not a number.
45
+ *
46
+ * @param left Dividend.
47
+ * @param right Divisor.
48
+ * @param mode Rounding mode.
49
+ * @returns The rounded quotient.
50
+ */
51
+ export declare const divideParts: (left: DecimalParts, right: DecimalParts, mode: RoundingMode) => DecimalParts;
52
+ /**
53
+ * The remainder of a division truncated towards zero, carrying the sign of the
54
+ * dividend — what `%` computes on numbers.
55
+ *
56
+ * Always exact: the remainder is smaller than the divisor and no finer than the
57
+ * finer of the two operands, so it always fits the format.
58
+ *
59
+ * @param left Dividend.
60
+ * @param right Divisor.
61
+ * @returns The remainder.
62
+ */
63
+ export declare const remainderParts: (left: DecimalParts, right: DecimalParts) => DecimalParts;
64
+ /**
65
+ * Compares two values in magnitude and sign.
66
+ *
67
+ * @param left First operand.
68
+ * @param right Second operand.
69
+ * @returns -1, 0 or 1, or `undefined` when either is not a number, which is
70
+ * ordered against nothing.
71
+ */
72
+ export declare const compareParts: (left: DecimalParts, right: DecimalParts) => -1 | 0 | 1 | undefined;
73
+ /**
74
+ * Raises a value to an integer power, with the special cases of IEEE 754's
75
+ * `pown`: anything to the power zero is one, `NaN` included; a zero or an
76
+ * infinity to a negative power swaps with the other; and the sign is negative
77
+ * only for a negative base and an odd power.
78
+ *
79
+ * The finite case is computed exactly and rounded once, so the result is the
80
+ * correctly rounded one — up to {@link EXACT_DIGIT_LIMIT} digits of exact
81
+ * power, past which it carries {@link GUARD_DIGITS} guard digits instead. A
82
+ * result that plainly overflows or underflows is settled from its logarithm,
83
+ * without building a number only to discard it.
84
+ *
85
+ * @param base Value raised.
86
+ * @param power Integer exponent.
87
+ * @param mode Rounding mode.
88
+ * @returns The rounded power.
89
+ */
90
+ export declare const powerParts: (base: DecimalParts, power: bigint, mode: RoundingMode) => DecimalParts;
91
+ /**
92
+ * Rounds a value to a number of places after the point.
93
+ *
94
+ * @param parts Value to round.
95
+ * @param places Places after the point; negative to round to tens, hundreds…
96
+ * @param mode Rounding mode.
97
+ * @returns The rounded value.
98
+ */
99
+ export declare const roundParts: (parts: DecimalParts, places: number, mode: RoundingMode) => DecimalParts;