@fulcro/types 0.1.0 → 0.3.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 CHANGED
@@ -1,15 +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.
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 CHANGED
@@ -1,66 +1,86 @@
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).
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
+ ## Structs
63
+
64
+ Value types with a fixed layout, built from the types above and from each
65
+ other — frozen, compared by field, and stored in a `DataView` without an object
66
+ per value:
67
+
68
+ ```ts
69
+ import { SinglePrecisionFloat, struct, type Struct } from '@fulcro/types';
70
+
71
+ const Vector3 = struct('Vector3', {
72
+ x: SinglePrecisionFloat,
73
+ y: SinglePrecisionFloat,
74
+ z: SinglePrecisionFloat,
75
+ });
76
+ type Vector3 = Struct<typeof Vector3>;
77
+
78
+ Vector3.layout.size; // 12, and sizeOf<Vector3>() at compile time
79
+ Vector3.write(view, 0, Vector3.from({ x: 0, y: 1, z: 0 }));
80
+ ```
81
+
82
+ ---
83
+
84
+ **Full guide:** [docs/types.md](../../docs/types.md) — ranges, rounding, the
85
+ operators, the special values, the layout table and structs.
86
+ 🇧🇷 [Leia em português](../../docs/pt-BR/types.md).
package/dist/index.d.ts CHANGED
@@ -19,4 +19,6 @@ export type { BoundedNumericType, NumericType } from './numericType/index.js';
19
19
  export type { RoundingMode } from './roundingMode/index.js';
20
20
  export { SignedInteger } from './signedInteger/index.js';
21
21
  export { SinglePrecisionFloat } from './singlePrecisionFloat/index.js';
22
+ export type { Struct, StructType } from './struct/index.js';
23
+ export { struct } from './struct/index.js';
22
24
  export { UnsignedInteger } from './unsignedInteger/index.js';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.UnsignedInteger = exports.SinglePrecisionFloat = exports.SignedInteger = exports.HalfPrecisionFloat = exports.DoublePrecisionFloat = exports.Decimal = exports.BigInteger = void 0;
3
+ exports.UnsignedInteger = exports.struct = exports.SinglePrecisionFloat = exports.SignedInteger = exports.HalfPrecisionFloat = exports.DoublePrecisionFloat = exports.Decimal = exports.BigInteger = void 0;
4
4
  /**
5
5
  * Public entry point of the package, matching the `main` and `types` fields of
6
6
  * `package.json`.
@@ -25,5 +25,7 @@ var signedInteger_1 = require("./signedInteger/index.js");
25
25
  Object.defineProperty(exports, "SignedInteger", { enumerable: true, get: function () { return signedInteger_1.SignedInteger; } });
26
26
  var singlePrecisionFloat_1 = require("./singlePrecisionFloat/index.js");
27
27
  Object.defineProperty(exports, "SinglePrecisionFloat", { enumerable: true, get: function () { return singlePrecisionFloat_1.SinglePrecisionFloat; } });
28
+ var struct_1 = require("./struct/index.js");
29
+ Object.defineProperty(exports, "struct", { enumerable: true, get: function () { return struct_1.struct; } });
28
30
  var unsignedInteger_1 = require("./unsignedInteger/index.js");
29
31
  Object.defineProperty(exports, "UnsignedInteger", { enumerable: true, get: function () { return unsignedInteger_1.UnsignedInteger; } });
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Arithmetic on number literals, at the type level, for the layout a struct
3
+ * type declares.
4
+ *
5
+ * `sizeOf<T>()` answers from the literal in `T['~layout']`, so a struct type
6
+ * has to carry its size as a literal, and that literal is a sum of its fields
7
+ * rounded up to its alignment. TypeScript has no arithmetic on literals; this
8
+ * module is the smallest amount of it the layout needs.
9
+ *
10
+ * Addition works digit by digit on the decimal text of a number, so it is not
11
+ * bounded by the recursion limit the way counting a tuple up to the sum would
12
+ * be. Rounding needs a remainder, and the remainder by an alignment — a power
13
+ * of two up to sixteen — depends only on the last four digits, since sixteen
14
+ * divides ten thousand; so no tuple built here is ever longer than 9,999.
15
+ *
16
+ * Wherever an input is `number` rather than a literal, the answer is `number`:
17
+ * `sizeOf` then refuses the type rather than report a wrong size.
18
+ */
19
+ /** A decimal digit, as text. */
20
+ type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
21
+ /** The value of each digit. */
22
+ interface DigitValues {
23
+ readonly '0': 0;
24
+ readonly '1': 1;
25
+ readonly '2': 2;
26
+ readonly '3': 3;
27
+ readonly '4': 4;
28
+ readonly '5': 5;
29
+ readonly '6': 6;
30
+ readonly '7': 7;
31
+ readonly '8': 8;
32
+ readonly '9': 9;
33
+ }
34
+ /**
35
+ * A tuple of `N` elements, for the small `N` a digit or an alignment needs.
36
+ *
37
+ * @template N Length, from 0 to 19.
38
+ */
39
+ type Units<N extends number, TAccumulated extends unknown[] = []> = TAccumulated['length'] extends N ? TAccumulated : Units<N, [...TAccumulated, unknown]>;
40
+ /** Ten elements, the carry out of a digit. */
41
+ type Ten = Units<10>;
42
+ /**
43
+ * Adds two digits and a carry.
44
+ *
45
+ * @template TLeft First digit.
46
+ * @template TRight Second digit.
47
+ * @template TCarry Carry into this digit.
48
+ * @returns `[digit, carry]` of the sum.
49
+ */
50
+ type AddDigits<TLeft extends Digit, TRight extends Digit, TCarry extends 0 | 1> = [
51
+ ...Units<DigitValues[TLeft]>,
52
+ ...Units<DigitValues[TRight]>,
53
+ ...Units<TCarry>
54
+ ] extends infer TAll extends unknown[] ? TAll extends [...Ten, ...infer TRest] ? [`${TRest['length']}`, 1] : [`${TAll['length']}`, 0] : never;
55
+ /** First character of a text, `'0'` once it is empty. */
56
+ type Head<TText extends string> = TText extends `${infer THead extends Digit}${string}` ? THead : '0';
57
+ /** A text without its first character. */
58
+ type Tail<TText extends string> = TText extends `${Digit}${infer TTail}` ? TTail : '';
59
+ /**
60
+ * Adds two numbers written least significant digit first.
61
+ *
62
+ * @returns The sum, least significant digit first.
63
+ */
64
+ type AddReversed<TLeft extends string, TRight extends string, TCarry extends 0 | 1 = 0, TSum extends string = ''> = TLeft extends '' ? TRight extends '' ? TCarry extends 1 ? `${TSum}1` : TSum : AddStep<TLeft, TRight, TCarry, TSum> : AddStep<TLeft, TRight, TCarry, TSum>;
65
+ /** One digit of {@link AddReversed}. */
66
+ type AddStep<TLeft extends string, TRight extends string, TCarry extends 0 | 1, TSum extends string> = AddDigits<Head<TLeft>, Head<TRight>, TCarry> extends [
67
+ infer TDigit extends string,
68
+ infer TNext extends 0 | 1
69
+ ] ? AddReversed<Tail<TLeft>, Tail<TRight>, TNext, `${TSum}${TDigit}`> : never;
70
+ /** A text, backwards. */
71
+ type Reverse<TText extends string, TReversed extends string = ''> = TText extends `${infer THead}${infer TTail}` ? Reverse<TTail, `${THead}${TReversed}`> : TReversed;
72
+ /** The number a text of digits spells. */
73
+ type ToNumber<TText extends string> = TText extends `${infer N extends number}` ? N : never;
74
+ /**
75
+ * The sum of two number literals; `number` when either is not a literal.
76
+ *
77
+ * @template TLeft First operand, a non-negative integer.
78
+ * @template TRight Second operand, a non-negative integer.
79
+ */
80
+ export type Add<TLeft extends number, TRight extends number> = number extends TLeft | TRight ? number : ToNumber<Reverse<AddReversed<Reverse<`${TLeft}`>, Reverse<`${TRight}`>>>>;
81
+ /**
82
+ * A tuple as long as a number of up to four digits, built one digit at a time
83
+ * — ten copies of what came before, and the digit — so its depth is the number
84
+ * of digits rather than the number itself.
85
+ */
86
+ type TupleOf<TDigits extends string, TAccumulated extends unknown[] = []> = TDigits extends `${infer THead extends Digit}${infer TTail}` ? TupleOf<TTail, [
87
+ ...TAccumulated,
88
+ ...TAccumulated,
89
+ ...TAccumulated,
90
+ ...TAccumulated,
91
+ ...TAccumulated,
92
+ ...TAccumulated,
93
+ ...TAccumulated,
94
+ ...TAccumulated,
95
+ ...TAccumulated,
96
+ ...TAccumulated,
97
+ ...Units<DigitValues[THead]>
98
+ ]> : TAccumulated;
99
+ /** The last `N` characters of a text written least significant first. */
100
+ type Take<TText extends string, N extends number, TTaken extends string = '', TCount extends unknown[] = []> = TCount['length'] extends N ? TTaken : TText extends `${infer THead}${infer TTail}` ? Take<TTail, N, `${THead}${TTaken}`, [...TCount, unknown]> : TTaken;
101
+ /** Digits of a number that decide its remainder by each alignment. */
102
+ interface RemainderDigits {
103
+ readonly 2: 1;
104
+ readonly 4: 2;
105
+ readonly 8: 3;
106
+ readonly 16: 4;
107
+ }
108
+ /** How many elements are left of a tuple once whole chunks are taken off it. */
109
+ type Leftover<TTuple extends unknown[], TChunk extends unknown[]> = TTuple extends [...TChunk, ...infer TRest] ? Leftover<TRest, TChunk> : TTuple['length'];
110
+ /**
111
+ * The remainder of a number by an alignment.
112
+ *
113
+ * @template N A non-negative integer literal.
114
+ * @template TAlignment 2, 4, 8 or 16.
115
+ */
116
+ type Remainder<N extends number, TAlignment extends keyof RemainderDigits> = Leftover<TupleOf<Take<Reverse<`${N}`>, RemainderDigits[TAlignment]>>, Units<TAlignment>>;
117
+ /**
118
+ * A number rounded up to a multiple of an alignment: the size of a struct once
119
+ * its tail padding is added.
120
+ *
121
+ * @template N Size before padding.
122
+ * @template TAlignment Alignment of the struct, a power of two up to 16.
123
+ */
124
+ export type RoundUp<N extends number, TAlignment extends number> = number extends N | TAlignment ? number : TAlignment extends keyof RemainderDigits ? Remainder<N, TAlignment> extends infer TRemainder extends number ? TRemainder extends 0 ? N : Units<TAlignment> extends [...Units<TRemainder>, ...infer TPadding] ? Add<N, TPadding['length']> : never : never : N;
125
+ /**
126
+ * The largest of a union of alignments.
127
+ *
128
+ * @template TAlignments Alignments of the fields, each a power of two up to 16.
129
+ */
130
+ export type LargestAlignment<TAlignments extends number> = number extends TAlignments ? number : 16 extends TAlignments ? 16 : 8 extends TAlignments ? 8 : 4 extends TAlignments ? 4 : 2 extends TAlignments ? 2 : 1;
131
+ export {};
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ /**
3
+ * Arithmetic on number literals, at the type level, for the layout a struct
4
+ * type declares.
5
+ *
6
+ * `sizeOf<T>()` answers from the literal in `T['~layout']`, so a struct type
7
+ * has to carry its size as a literal, and that literal is a sum of its fields
8
+ * rounded up to its alignment. TypeScript has no arithmetic on literals; this
9
+ * module is the smallest amount of it the layout needs.
10
+ *
11
+ * Addition works digit by digit on the decimal text of a number, so it is not
12
+ * bounded by the recursion limit the way counting a tuple up to the sum would
13
+ * be. Rounding needs a remainder, and the remainder by an alignment — a power
14
+ * of two up to sixteen — depends only on the last four digits, since sixteen
15
+ * divides ten thousand; so no tuple built here is ever longer than 9,999.
16
+ *
17
+ * Wherever an input is `number` rather than a literal, the answer is `number`:
18
+ * `sizeOf` then refuses the type rather than report a wrong size.
19
+ */
20
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,58 @@
1
+ /**
2
+ * How a field of a struct is laid out in bytes, and how it is compared.
3
+ *
4
+ * Internal. A struct is built from descriptors, and a descriptor says how a
5
+ * value is checked but not how it is stored — `SinglePrecisionFloat` has no
6
+ * byte order. The codec is what a struct looks up for each field, by the
7
+ * identity of its descriptor, so the numeric types did not have to grow a
8
+ * public byte encoding for this.
9
+ *
10
+ * Every multi-byte value is little-endian, whatever the platform: bytes a
11
+ * struct writes on one machine read back the same on any other, and it is the
12
+ * order of every platform a JavaScript engine runs on in practice.
13
+ */
14
+ export interface FieldCodec {
15
+ /** Size, in bytes. */
16
+ readonly size: number;
17
+ /** Alignment, in bytes. */
18
+ readonly alignment: number;
19
+ /**
20
+ * Reads a value.
21
+ *
22
+ * @param view Bytes to read from.
23
+ * @param offset Where the value starts.
24
+ * @returns The value.
25
+ */
26
+ read(view: DataView, offset: number): unknown;
27
+ /**
28
+ * Writes a value.
29
+ *
30
+ * @param view Bytes to write into.
31
+ * @param offset Where the value starts.
32
+ * @param value The value, already of the field's type.
33
+ */
34
+ write(view: DataView, offset: number, value: unknown): void;
35
+ /**
36
+ * Compares two values as the field's own type does.
37
+ *
38
+ * @param left First value.
39
+ * @param right Second value.
40
+ * @returns `true` when they are equal.
41
+ */
42
+ equals(left: unknown, right: unknown): boolean;
43
+ }
44
+ /**
45
+ * Registers the codec of a struct, so that another struct can hold it as a
46
+ * field.
47
+ *
48
+ * @param descriptor Descriptor of the struct.
49
+ * @param codec How it is stored.
50
+ */
51
+ export declare const registerStructCodec: (descriptor: object, codec: FieldCodec) => void;
52
+ /**
53
+ * The codec of a field's descriptor.
54
+ *
55
+ * @param descriptor What the field was declared with.
56
+ * @returns Its codec, or `undefined` for a descriptor with no fixed layout.
57
+ */
58
+ export declare const codecOf: (descriptor: unknown) => FieldCodec | undefined;