ts-data-forge 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.
Files changed (143) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +534 -0
  3. package/package.json +101 -0
  4. package/src/array/array-utils-creation.test.mts +443 -0
  5. package/src/array/array-utils-modification.test.mts +197 -0
  6. package/src/array/array-utils-overload-type-error.test.mts +149 -0
  7. package/src/array/array-utils-reducing-value.test.mts +425 -0
  8. package/src/array/array-utils-search.test.mts +169 -0
  9. package/src/array/array-utils-set-op.test.mts +335 -0
  10. package/src/array/array-utils-slice-clamped.test.mts +113 -0
  11. package/src/array/array-utils-slicing.test.mts +316 -0
  12. package/src/array/array-utils-transformation.test.mts +790 -0
  13. package/src/array/array-utils-validation.test.mts +492 -0
  14. package/src/array/array-utils.mts +4000 -0
  15. package/src/array/array.test.mts +146 -0
  16. package/src/array/index.mts +2 -0
  17. package/src/array/tuple-utils.mts +519 -0
  18. package/src/array/tuple-utils.test.mts +518 -0
  19. package/src/collections/imap-mapped.mts +801 -0
  20. package/src/collections/imap-mapped.test.mts +860 -0
  21. package/src/collections/imap.mts +651 -0
  22. package/src/collections/imap.test.mts +932 -0
  23. package/src/collections/index.mts +6 -0
  24. package/src/collections/iset-mapped.mts +889 -0
  25. package/src/collections/iset-mapped.test.mts +1187 -0
  26. package/src/collections/iset.mts +682 -0
  27. package/src/collections/iset.test.mts +1084 -0
  28. package/src/collections/queue.mts +390 -0
  29. package/src/collections/queue.test.mts +282 -0
  30. package/src/collections/stack.mts +423 -0
  31. package/src/collections/stack.test.mts +225 -0
  32. package/src/expect-type.mts +206 -0
  33. package/src/functional/index.mts +4 -0
  34. package/src/functional/match.mts +300 -0
  35. package/src/functional/match.test.mts +177 -0
  36. package/src/functional/optional.mts +733 -0
  37. package/src/functional/optional.test.mts +619 -0
  38. package/src/functional/pipe.mts +212 -0
  39. package/src/functional/pipe.test.mts +85 -0
  40. package/src/functional/result.mts +1134 -0
  41. package/src/functional/result.test.mts +777 -0
  42. package/src/globals.d.mts +38 -0
  43. package/src/guard/has-key.mts +119 -0
  44. package/src/guard/has-key.test.mts +219 -0
  45. package/src/guard/index.mts +7 -0
  46. package/src/guard/is-non-empty-string.mts +108 -0
  47. package/src/guard/is-non-empty-string.test.mts +91 -0
  48. package/src/guard/is-non-null-object.mts +106 -0
  49. package/src/guard/is-non-null-object.test.mts +90 -0
  50. package/src/guard/is-primitive.mts +165 -0
  51. package/src/guard/is-primitive.test.mts +102 -0
  52. package/src/guard/is-record.mts +153 -0
  53. package/src/guard/is-record.test.mts +112 -0
  54. package/src/guard/is-type.mts +450 -0
  55. package/src/guard/is-type.test.mts +496 -0
  56. package/src/guard/key-is-in.mts +163 -0
  57. package/src/guard/key-is-in.test.mts +19 -0
  58. package/src/index.mts +10 -0
  59. package/src/iterator/index.mts +1 -0
  60. package/src/iterator/range.mts +120 -0
  61. package/src/iterator/range.test.mts +33 -0
  62. package/src/json/index.mts +1 -0
  63. package/src/json/json.mts +711 -0
  64. package/src/json/json.test.mts +628 -0
  65. package/src/number/branded-types/finite-number.mts +354 -0
  66. package/src/number/branded-types/finite-number.test.mts +135 -0
  67. package/src/number/branded-types/index.mts +26 -0
  68. package/src/number/branded-types/int.mts +278 -0
  69. package/src/number/branded-types/int.test.mts +140 -0
  70. package/src/number/branded-types/int16.mts +192 -0
  71. package/src/number/branded-types/int16.test.mts +170 -0
  72. package/src/number/branded-types/int32.mts +193 -0
  73. package/src/number/branded-types/int32.test.mts +170 -0
  74. package/src/number/branded-types/non-negative-finite-number.mts +223 -0
  75. package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
  76. package/src/number/branded-types/non-negative-int16.mts +187 -0
  77. package/src/number/branded-types/non-negative-int16.test.mts +201 -0
  78. package/src/number/branded-types/non-negative-int32.mts +187 -0
  79. package/src/number/branded-types/non-negative-int32.test.mts +204 -0
  80. package/src/number/branded-types/non-zero-finite-number.mts +229 -0
  81. package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
  82. package/src/number/branded-types/non-zero-int.mts +167 -0
  83. package/src/number/branded-types/non-zero-int.test.mts +177 -0
  84. package/src/number/branded-types/non-zero-int16.mts +196 -0
  85. package/src/number/branded-types/non-zero-int16.test.mts +195 -0
  86. package/src/number/branded-types/non-zero-int32.mts +196 -0
  87. package/src/number/branded-types/non-zero-int32.test.mts +197 -0
  88. package/src/number/branded-types/non-zero-safe-int.mts +196 -0
  89. package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
  90. package/src/number/branded-types/non-zero-uint16.mts +189 -0
  91. package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
  92. package/src/number/branded-types/non-zero-uint32.mts +189 -0
  93. package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
  94. package/src/number/branded-types/positive-finite-number.mts +241 -0
  95. package/src/number/branded-types/positive-finite-number.test.mts +204 -0
  96. package/src/number/branded-types/positive-int.mts +304 -0
  97. package/src/number/branded-types/positive-int.test.mts +176 -0
  98. package/src/number/branded-types/positive-int16.mts +188 -0
  99. package/src/number/branded-types/positive-int16.test.mts +197 -0
  100. package/src/number/branded-types/positive-int32.mts +188 -0
  101. package/src/number/branded-types/positive-int32.test.mts +197 -0
  102. package/src/number/branded-types/positive-safe-int.mts +187 -0
  103. package/src/number/branded-types/positive-safe-int.test.mts +210 -0
  104. package/src/number/branded-types/positive-uint16.mts +188 -0
  105. package/src/number/branded-types/positive-uint16.test.mts +203 -0
  106. package/src/number/branded-types/positive-uint32.mts +188 -0
  107. package/src/number/branded-types/positive-uint32.test.mts +203 -0
  108. package/src/number/branded-types/safe-int.mts +291 -0
  109. package/src/number/branded-types/safe-int.test.mts +170 -0
  110. package/src/number/branded-types/safe-uint.mts +187 -0
  111. package/src/number/branded-types/safe-uint.test.mts +176 -0
  112. package/src/number/branded-types/uint.mts +179 -0
  113. package/src/number/branded-types/uint.test.mts +158 -0
  114. package/src/number/branded-types/uint16.mts +186 -0
  115. package/src/number/branded-types/uint16.test.mts +170 -0
  116. package/src/number/branded-types/uint32.mts +218 -0
  117. package/src/number/branded-types/uint32.test.mts +170 -0
  118. package/src/number/enum/index.mts +2 -0
  119. package/src/number/enum/int8.mts +344 -0
  120. package/src/number/enum/int8.test.mts +180 -0
  121. package/src/number/enum/uint8.mts +293 -0
  122. package/src/number/enum/uint8.test.mts +164 -0
  123. package/src/number/index.mts +4 -0
  124. package/src/number/num.mts +604 -0
  125. package/src/number/num.test.mts +242 -0
  126. package/src/number/refined-number-utils.mts +566 -0
  127. package/src/object/index.mts +1 -0
  128. package/src/object/object.mts +447 -0
  129. package/src/object/object.test.mts +124 -0
  130. package/src/others/cast-mutable.mts +113 -0
  131. package/src/others/cast-readonly.mts +192 -0
  132. package/src/others/cast-readonly.test.mts +89 -0
  133. package/src/others/if-then.mts +98 -0
  134. package/src/others/if-then.test.mts +75 -0
  135. package/src/others/index.mts +7 -0
  136. package/src/others/map-nullable.mts +172 -0
  137. package/src/others/map-nullable.test.mts +297 -0
  138. package/src/others/memoize-function.mts +196 -0
  139. package/src/others/memoize-function.test.mts +168 -0
  140. package/src/others/tuple.mts +160 -0
  141. package/src/others/tuple.test.mts +11 -0
  142. package/src/others/unknown-to-string.mts +215 -0
  143. package/src/others/unknown-to-string.test.mts +114 -0
@@ -0,0 +1,203 @@
1
+ import { expectType } from '../../expect-type.mjs';
2
+ import {
3
+ asPositiveUint32,
4
+ isPositiveUint32,
5
+ PositiveUint32,
6
+ } from './positive-uint32.mjs';
7
+
8
+ describe('PositiveUint32', () => {
9
+ describe('asPositiveUint32', () => {
10
+ test('accepts valid positive uint32 values', () => {
11
+ expect(() => asPositiveUint32(1)).not.toThrow();
12
+ expect(() => asPositiveUint32(1000)).not.toThrow();
13
+ expect(() => asPositiveUint32(4294967295)).not.toThrow(); // 2^32 - 1
14
+ expect(() => asPositiveUint32(2147483648)).not.toThrow(); // 2^31
15
+ });
16
+
17
+ test('rejects zero', () => {
18
+ expect(() => asPositiveUint32(0)).toThrow(TypeError);
19
+ });
20
+
21
+ test('rejects values outside uint32 range', () => {
22
+ expect(() => asPositiveUint32(4294967296)).toThrow(TypeError); // 2^32
23
+ expect(() => asPositiveUint32(10000000000)).toThrow(TypeError);
24
+ });
25
+
26
+ test('rejects negative integers', () => {
27
+ expect(() => asPositiveUint32(-1)).toThrow(TypeError);
28
+ expect(() => asPositiveUint32(-42)).toThrow(TypeError);
29
+ });
30
+
31
+ test('rejects non-integers', () => {
32
+ expect(() => asPositiveUint32(Number.NaN)).toThrow(TypeError);
33
+ expect(() => asPositiveUint32(Number.POSITIVE_INFINITY)).toThrow(
34
+ TypeError,
35
+ );
36
+ expect(() => asPositiveUint32(Number.NEGATIVE_INFINITY)).toThrow(
37
+ TypeError,
38
+ );
39
+ expect(() => asPositiveUint32(1.2)).toThrow(TypeError);
40
+ expect(() => asPositiveUint32(-3.4)).toThrow(TypeError);
41
+ });
42
+
43
+ test('returns the same value for valid inputs', () => {
44
+ expect(asPositiveUint32(5)).toBe(5);
45
+ expect(asPositiveUint32(1)).toBe(1);
46
+ expect(asPositiveUint32(4294967295)).toBe(4294967295);
47
+ });
48
+
49
+ test.each([
50
+ { name: 'Number.NaN', value: Number.NaN },
51
+ { name: 'Number.POSITIVE_INFINITY', value: Number.POSITIVE_INFINITY },
52
+ { name: 'Number.NEGATIVE_INFINITY', value: Number.NEGATIVE_INFINITY },
53
+ { name: '1.2', value: 1.2 },
54
+ { name: '-3.4', value: -3.4 },
55
+ { name: '0', value: 0 },
56
+ { name: '-1', value: -1 },
57
+ { name: '4294967296', value: 4294967296 },
58
+ ] as const)(
59
+ `asPositiveUint32($name) should throw a TypeError`,
60
+ ({ value }) => {
61
+ expect(() => asPositiveUint32(value)).toThrow(
62
+ new TypeError(
63
+ `Expected a positive integer in [1, 2^32), got: ${value}`,
64
+ ),
65
+ );
66
+ },
67
+ );
68
+ });
69
+
70
+ describe('isPositiveUint32', () => {
71
+ test('correctly identifies positive uint32 values', () => {
72
+ expect(isPositiveUint32(1)).toBe(true);
73
+ expect(isPositiveUint32(1000)).toBe(true);
74
+ expect(isPositiveUint32(4294967295)).toBe(true);
75
+ expect(isPositiveUint32(2147483648)).toBe(true);
76
+ });
77
+
78
+ test('correctly identifies zero', () => {
79
+ expect(isPositiveUint32(0)).toBe(false);
80
+ });
81
+
82
+ test('correctly identifies values outside uint32 range', () => {
83
+ expect(isPositiveUint32(4294967296)).toBe(false);
84
+ expect(isPositiveUint32(10000000000)).toBe(false);
85
+ });
86
+
87
+ test('correctly identifies negative integers', () => {
88
+ expect(isPositiveUint32(-1)).toBe(false);
89
+ expect(isPositiveUint32(-42)).toBe(false);
90
+ });
91
+
92
+ test('correctly identifies non-integers', () => {
93
+ expect(isPositiveUint32(Number.NaN)).toBe(false);
94
+ expect(isPositiveUint32(Number.POSITIVE_INFINITY)).toBe(false);
95
+ expect(isPositiveUint32(Number.NEGATIVE_INFINITY)).toBe(false);
96
+ expect(isPositiveUint32(1.2)).toBe(false);
97
+ expect(isPositiveUint32(-3.4)).toBe(false);
98
+ });
99
+ });
100
+
101
+ describe('PositiveUint32.is', () => {
102
+ test('same as isPositiveUint32 function', () => {
103
+ expect(PositiveUint32.is(5)).toBe(isPositiveUint32(5));
104
+ expect(PositiveUint32.is(0)).toBe(isPositiveUint32(0));
105
+ expect(PositiveUint32.is(-1)).toBe(isPositiveUint32(-1));
106
+ });
107
+ });
108
+
109
+ describe('constants', () => {
110
+ test('MIN_VALUE and MAX_VALUE', () => {
111
+ expect(PositiveUint32.MIN_VALUE).toBe(1);
112
+ expect(PositiveUint32.MAX_VALUE).toBe(4294967295);
113
+ });
114
+ });
115
+
116
+ describe('mathematical operations', () => {
117
+ const a = asPositiveUint32(1000000);
118
+ const b = asPositiveUint32(500000);
119
+
120
+ test('min and max', () => {
121
+ expect(PositiveUint32.min(a, b)).toBe(500000);
122
+ expect(PositiveUint32.max(a, b)).toBe(1000000);
123
+ });
124
+
125
+ test('add (with clamping to positive uint32 range)', () => {
126
+ const result = PositiveUint32.add(
127
+ asPositiveUint32(4294967000),
128
+ asPositiveUint32(1000),
129
+ );
130
+ expect(result).toBe(4294967295); // clamped to max
131
+ expect(PositiveUint32.add(a, b)).toBe(1500000);
132
+ });
133
+
134
+ test('sub (never goes below 1)', () => {
135
+ expect(PositiveUint32.sub(a, b)).toBe(500000);
136
+ expect(PositiveUint32.sub(b, a)).toBe(1); // clamped to 1
137
+ });
138
+
139
+ test('mul (with clamping to positive uint32 range)', () => {
140
+ const result = PositiveUint32.mul(
141
+ asPositiveUint32(100000),
142
+ asPositiveUint32(100000),
143
+ );
144
+ expect(result).toBe(4294967295); // clamped to max
145
+ expect(
146
+ PositiveUint32.mul(asPositiveUint32(1000), asPositiveUint32(5)),
147
+ ).toBe(5000);
148
+ });
149
+
150
+ test('div (floor division, never goes below 1)', () => {
151
+ expect(PositiveUint32.div(a, asPositiveUint32(500000))).toBe(2);
152
+ expect(PositiveUint32.div(asPositiveUint32(7), asPositiveUint32(3))).toBe(
153
+ 2,
154
+ );
155
+ expect(
156
+ PositiveUint32.div(asPositiveUint32(500000), asPositiveUint32(1000000)),
157
+ ).toBe(1); // floor(500000/1000000) = 0, clamped to 1
158
+ });
159
+
160
+ test('pow (with clamping to positive uint32 range)', () => {
161
+ const result = PositiveUint32.pow(
162
+ asPositiveUint32(10000),
163
+ asPositiveUint32(3),
164
+ );
165
+ expect(result).toBe(4294967295); // clamped to max
166
+ expect(PositiveUint32.pow(asPositiveUint32(2), asPositiveUint32(3))).toBe(
167
+ 8,
168
+ );
169
+ });
170
+ });
171
+
172
+ describe('random', () => {
173
+ test('generates positive uint32 values within specified range', () => {
174
+ const min = 1;
175
+ const max = 20;
176
+
177
+ for (let i = 0; i < 10; i++) {
178
+ const result = PositiveUint32.random(min, max);
179
+ expect(result).toBeGreaterThanOrEqual(min);
180
+ expect(result).toBeLessThanOrEqual(max);
181
+ expect(PositiveUint32.is(result)).toBe(true);
182
+ expect(Number.isInteger(result)).toBe(true);
183
+ expect(result).toBeGreaterThan(0);
184
+ }
185
+ });
186
+
187
+ test('generates values within PositiveUint32 range', () => {
188
+ for (let i = 0; i < 10; i++) {
189
+ const result = PositiveUint32.random(1, 30);
190
+ expect(result).toBeGreaterThanOrEqual(1);
191
+ expect(result).toBeLessThanOrEqual(4294967295);
192
+ }
193
+ });
194
+ });
195
+
196
+ describe('type assertions', () => {
197
+ test('type relationships', () => {
198
+ expectType<PositiveUint32, number>('<=');
199
+
200
+ expectTypeOf(asPositiveUint32(1000000)).toExtend<PositiveUint32>();
201
+ });
202
+ });
203
+ });
@@ -0,0 +1,291 @@
1
+ import { expectType } from '../../expect-type.mjs';
2
+ import { TsVerifiedInternals } from '../refined-number-utils.mjs';
3
+
4
+ type ElementType = SafeInt;
5
+
6
+ const typeNameInMessage = 'a safe integer';
7
+
8
+ const {
9
+ MIN_VALUE,
10
+ MAX_VALUE,
11
+ abs,
12
+ min: min_,
13
+ max: max_,
14
+ pow,
15
+ add,
16
+ sub,
17
+ mul,
18
+ div,
19
+ random,
20
+ is,
21
+ castType,
22
+ clamp,
23
+ } = TsVerifiedInternals.RefinedNumberUtils.operatorsForInteger<
24
+ ElementType,
25
+ SafeInt,
26
+ SafeUint
27
+ >({
28
+ integerOrSafeInteger: 'SafeInteger',
29
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
30
+ MIN_VALUE: Number.MIN_SAFE_INTEGER as SafeInt,
31
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
32
+ MAX_VALUE: Number.MAX_SAFE_INTEGER as SafeUint,
33
+ typeNameInMessage,
34
+ } as const);
35
+
36
+ /**
37
+ * Type guard that checks if a value is a safe integer.
38
+ *
39
+ * A safe integer is an integer that can be exactly represented in JavaScript
40
+ * without precision loss. The range is [±(2^53 - 1)].
41
+ *
42
+ * @param value - The value to check
43
+ * @returns `true` if the value is a safe integer, `false` otherwise
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * isSafeInt(42); // true
48
+ * isSafeInt(Number.MAX_SAFE_INTEGER); // true
49
+ * isSafeInt(Number.MAX_SAFE_INTEGER + 1); // false
50
+ * isSafeInt(3.14); // false
51
+ * isSafeInt(NaN); // false
52
+ * ```
53
+ */
54
+ export const isSafeInt = is;
55
+
56
+ /**
57
+ * Casts a number to a SafeInt branded type.
58
+ *
59
+ * This function validates that the input is a safe integer (within ±(2^53 - 1))
60
+ * and returns it with the SafeInt brand. This ensures type safety for operations
61
+ * that require precise integer arithmetic.
62
+ *
63
+ * @param value - The value to cast
64
+ * @returns The value as a SafeInt branded type
65
+ * @throws {TypeError} If the value is not a safe integer
66
+ *
67
+ * @example
68
+ * ```typescript
69
+ * const x = asSafeInt(5); // SafeInt
70
+ * const y = asSafeInt(-1000); // SafeInt
71
+ * const z = asSafeInt(2**50); // SafeInt (within range)
72
+ *
73
+ * // These throw TypeError:
74
+ * // asSafeInt(1.5); // Not an integer
75
+ * // asSafeInt(Number.MAX_SAFE_INTEGER + 1); // Exceeds safe range
76
+ * // asSafeInt(2**53); // Loss of precision
77
+ * ```
78
+ */
79
+ export const asSafeInt = castType;
80
+
81
+ /**
82
+ * Namespace providing type-safe operations for SafeInt branded types.
83
+ *
84
+ * SafeInt represents integers that can be exactly represented in JavaScript's
85
+ * number type without precision loss. The range is [±(2^53 - 1)], which covers
86
+ * approximately ±9 quadrillion.
87
+ *
88
+ * All operations automatically clamp results to stay within the safe range,
89
+ * preventing precision loss that occurs with larger integers. This makes SafeInt
90
+ * ideal for:
91
+ * - Financial calculations requiring exact cents
92
+ * - Database IDs and counters
93
+ * - Array indices and sizes
94
+ * - Any integer arithmetic requiring precision guarantees
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * // Near the boundary
99
+ * const nearMax = asSafeInt(9007199254740990);
100
+ * const increment = asSafeInt(10);
101
+ *
102
+ * // Automatic clamping prevents precision loss
103
+ * const sum = SafeInt.add(nearMax, increment); // Clamped to MAX_SAFE_INTEGER
104
+ * const product = SafeInt.mul(nearMax, increment); // Clamped to MAX_SAFE_INTEGER
105
+ *
106
+ * // Safe operations
107
+ * const a = asSafeInt(1000000);
108
+ * const b = asSafeInt(500);
109
+ *
110
+ * const diff = SafeInt.sub(a, b); // SafeInt (999500)
111
+ * const quotient = SafeInt.div(a, b); // SafeInt (2000)
112
+ * const power = SafeInt.pow(b, asSafeInt(2)); // SafeInt (250000)
113
+ *
114
+ * // Utility operations
115
+ * const absolute = SafeInt.abs(asSafeInt(-42)); // SafeInt (42)
116
+ * const clamped = SafeInt.clamp(2**60); // SafeInt (MAX_SAFE_INTEGER)
117
+ *
118
+ * // Random generation
119
+ * const die = SafeInt.random(asSafeInt(1), asSafeInt(6)); // Random 1-6
120
+ * ```
121
+ */
122
+ export const SafeInt = {
123
+ /**
124
+ * Type guard that checks if a value is a safe integer.
125
+ *
126
+ * @param value - The value to check
127
+ * @returns `true` if the value is a safe integer, `false` otherwise
128
+ *
129
+ * @see {@link isSafeInt} for usage examples
130
+ */
131
+ is,
132
+
133
+ /**
134
+ * The minimum safe integer value (-(2^53 - 1)).
135
+ * @readonly
136
+ */
137
+ MIN_VALUE,
138
+
139
+ /**
140
+ * The maximum safe integer value (2^53 - 1).
141
+ * @readonly
142
+ */
143
+ MAX_VALUE,
144
+
145
+ /**
146
+ * Returns the absolute value of a safe integer.
147
+ *
148
+ * Note: `Math.abs(MIN_SAFE_INTEGER)` would exceed `MAX_SAFE_INTEGER`,
149
+ * so this function clamps the result to maintain the safe integer guarantee.
150
+ *
151
+ * @param a - The safe integer value
152
+ * @returns The absolute value as a SafeInt, clamped if necessary
153
+ *
154
+ * @example
155
+ * ```typescript
156
+ * SafeInt.abs(asSafeInt(-42)); // SafeInt (42)
157
+ * SafeInt.abs(asSafeInt(42)); // SafeInt (42)
158
+ * SafeInt.abs(SafeInt.MIN_VALUE); // SafeInt (MAX_SAFE_INTEGER)
159
+ * ```
160
+ */
161
+ abs,
162
+
163
+ /**
164
+ * Returns the minimum value from a list of safe integers.
165
+ *
166
+ * @param values - The safe integers to compare (at least one required)
167
+ * @returns The smallest value as a SafeInt
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * SafeInt.min(asSafeInt(5), asSafeInt(3)); // SafeInt (3)
172
+ * SafeInt.min(asSafeInt(-10), asSafeInt(0), asSafeInt(10)); // SafeInt (-10)
173
+ * ```
174
+ */
175
+ min: min_,
176
+
177
+ /**
178
+ * Returns the maximum value from a list of safe integers.
179
+ *
180
+ * @param values - The safe integers to compare (at least one required)
181
+ * @returns The largest value as a SafeInt
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * SafeInt.max(asSafeInt(5), asSafeInt(3)); // SafeInt (5)
186
+ * SafeInt.max(asSafeInt(-10), asSafeInt(0), asSafeInt(10)); // SafeInt (10)
187
+ * ```
188
+ */
189
+ max: max_,
190
+
191
+ /**
192
+ * Clamps a number to the safe integer range.
193
+ * @param value The number to clamp.
194
+ * @returns The value clamped to [MIN_SAFE_INTEGER, MAX_SAFE_INTEGER] as a SafeInt.
195
+ */
196
+ clamp,
197
+
198
+ /**
199
+ * Generates a random safe integer within the specified range (inclusive).
200
+ *
201
+ * The range is inclusive on both ends. If min > max, they are automatically swapped.
202
+ *
203
+ * @param min - The minimum value (inclusive)
204
+ * @param max - The maximum value (inclusive)
205
+ * @returns A random SafeInt in the range [min, max]
206
+ *
207
+ * @example
208
+ * ```typescript
209
+ * // Dice roll
210
+ * const d20 = SafeInt.random(asSafeInt(1), asSafeInt(20));
211
+ *
212
+ * // Random index for large array
213
+ * const index = SafeInt.random(asSafeInt(0), asSafeInt(1000000));
214
+ *
215
+ * // Can use full safe range
216
+ * const any = SafeInt.random(SafeInt.MIN_VALUE, SafeInt.MAX_VALUE);
217
+ * ```
218
+ */
219
+ random,
220
+
221
+ /**
222
+ * Raises a SafeInt to the power of another SafeInt.
223
+ * @param a The base SafeInt.
224
+ * @param b The exponent SafeInt.
225
+ * @returns `a ** b` clamped to safe integer range as a SafeInt.
226
+ */
227
+ pow,
228
+
229
+ /**
230
+ * Adds two SafeInt values.
231
+ * @param a The first SafeInt.
232
+ * @param b The second SafeInt.
233
+ * @returns `a + b` clamped to safe integer range as a SafeInt.
234
+ */
235
+ add,
236
+
237
+ /**
238
+ * Subtracts one SafeInt from another.
239
+ * @param a The minuend SafeInt.
240
+ * @param b The subtrahend SafeInt.
241
+ * @returns `a - b` clamped to safe integer range as a SafeInt.
242
+ */
243
+ sub,
244
+
245
+ /**
246
+ * Multiplies two SafeInt values.
247
+ * @param a The first SafeInt.
248
+ * @param b The second SafeInt.
249
+ * @returns `a * b` clamped to safe integer range as a SafeInt.
250
+ */
251
+ mul,
252
+
253
+ /**
254
+ * Divides one SafeInt by another using floor division.
255
+ *
256
+ * Performs mathematical floor division: `⌊a / b⌋`.
257
+ * The divisor must be non-zero (enforced by type constraints).
258
+ *
259
+ * @param a - The dividend
260
+ * @param b - The divisor (must be non-zero)
261
+ * @returns The integer quotient as a SafeInt
262
+ *
263
+ * @example
264
+ * ```typescript
265
+ * SafeInt.div(asSafeInt(10), asSafeInt(3)); // SafeInt (3)
266
+ * SafeInt.div(asSafeInt(-10), asSafeInt(3)); // SafeInt (-4)
267
+ *
268
+ * // Large number division
269
+ * const large = asSafeInt(1000000000000);
270
+ * const divisor = asSafeInt(1000000);
271
+ * SafeInt.div(large, divisor); // SafeInt (1000000)
272
+ * ```
273
+ */
274
+ div,
275
+ } as const;
276
+
277
+ expectType<
278
+ keyof typeof SafeInt,
279
+ keyof TsVerifiedInternals.RefinedNumberUtils.NumberClass<
280
+ ElementType,
281
+ 'int' | 'range'
282
+ >
283
+ >('=');
284
+
285
+ expectType<
286
+ typeof SafeInt,
287
+ TsVerifiedInternals.RefinedNumberUtils.NumberClass<
288
+ ElementType,
289
+ 'int' | 'range'
290
+ >
291
+ >('<=');
@@ -0,0 +1,170 @@
1
+ import { expectType } from '../../expect-type.mjs';
2
+ import { asSafeInt, SafeInt } from './safe-int.mjs';
3
+
4
+ describe('SafeInt', () => {
5
+ describe('asSafeInt', () => {
6
+ test('accepts valid safe integers', () => {
7
+ expect(() => asSafeInt(0)).not.toThrow();
8
+ expect(() => asSafeInt(1)).not.toThrow();
9
+ expect(() => asSafeInt(-1)).not.toThrow();
10
+ expect(() => asSafeInt(42)).not.toThrow();
11
+ expect(() => asSafeInt(-42)).not.toThrow();
12
+ expect(() => asSafeInt(Number.MAX_SAFE_INTEGER)).not.toThrow();
13
+ expect(() => asSafeInt(Number.MIN_SAFE_INTEGER)).not.toThrow();
14
+ });
15
+
16
+ test('rejects values outside safe integer range', () => {
17
+ expect(() => asSafeInt(Number.MAX_SAFE_INTEGER + 1)).toThrow(TypeError);
18
+ expect(() => asSafeInt(Number.MIN_SAFE_INTEGER - 1)).toThrow(TypeError);
19
+ expect(() => asSafeInt(Number.MAX_VALUE)).toThrow(TypeError);
20
+ expect(() => asSafeInt(-Number.MAX_VALUE)).toThrow(TypeError);
21
+ });
22
+
23
+ test('rejects non-integers', () => {
24
+ expect(() => asSafeInt(Number.NaN)).toThrow(TypeError);
25
+ expect(() => asSafeInt(Number.POSITIVE_INFINITY)).toThrow(TypeError);
26
+ expect(() => asSafeInt(Number.NEGATIVE_INFINITY)).toThrow(TypeError);
27
+ expect(() => asSafeInt(1.2)).toThrow(TypeError);
28
+ expect(() => asSafeInt(-3.4)).toThrow(TypeError);
29
+ });
30
+
31
+ test('returns the same value for valid inputs', () => {
32
+ expect(asSafeInt(5)).toBe(5);
33
+ expect(asSafeInt(-10)).toBe(-10);
34
+ expect(asSafeInt(0)).toBe(0);
35
+ expect(asSafeInt(Number.MAX_SAFE_INTEGER)).toBe(Number.MAX_SAFE_INTEGER);
36
+ expect(asSafeInt(Number.MIN_SAFE_INTEGER)).toBe(Number.MIN_SAFE_INTEGER);
37
+ });
38
+
39
+ test.each([
40
+ { name: 'Number.NaN', value: Number.NaN },
41
+ { name: 'Number.POSITIVE_INFINITY', value: Number.POSITIVE_INFINITY },
42
+ { name: 'Number.NEGATIVE_INFINITY', value: Number.NEGATIVE_INFINITY },
43
+ { name: '1.2', value: 1.2 },
44
+ { name: '-3.4', value: -3.4 },
45
+ ] as const)(`asSafeInt($name) should throw a TypeError`, ({ value }) => {
46
+ expect(() => asSafeInt(value)).toThrow(
47
+ new TypeError(`Expected a safe integer, got: ${value}`),
48
+ );
49
+ });
50
+ });
51
+
52
+ describe('SafeInt.is', () => {
53
+ test('correctly identifies safe integers', () => {
54
+ expect(SafeInt.is(0)).toBe(true);
55
+ expect(SafeInt.is(1)).toBe(true);
56
+ expect(SafeInt.is(-1)).toBe(true);
57
+ expect(SafeInt.is(42)).toBe(true);
58
+ expect(SafeInt.is(-42)).toBe(true);
59
+ expect(SafeInt.is(Number.MAX_SAFE_INTEGER)).toBe(true);
60
+ expect(SafeInt.is(Number.MIN_SAFE_INTEGER)).toBe(true);
61
+ });
62
+
63
+ test('correctly identifies values outside safe integer range', () => {
64
+ expect(SafeInt.is(Number.MAX_SAFE_INTEGER + 1)).toBe(false);
65
+ expect(SafeInt.is(Number.MIN_SAFE_INTEGER - 1)).toBe(false);
66
+ expect(SafeInt.is(Number.MAX_VALUE)).toBe(false);
67
+ expect(SafeInt.is(-Number.MAX_VALUE)).toBe(false);
68
+ });
69
+
70
+ test('correctly identifies non-integers', () => {
71
+ expect(SafeInt.is(Number.NaN)).toBe(false);
72
+ expect(SafeInt.is(Number.POSITIVE_INFINITY)).toBe(false);
73
+ expect(SafeInt.is(Number.NEGATIVE_INFINITY)).toBe(false);
74
+ expect(SafeInt.is(1.2)).toBe(false);
75
+ expect(SafeInt.is(-3.4)).toBe(false);
76
+ });
77
+ });
78
+
79
+ describe('constants', () => {
80
+ test('MIN_VALUE and MAX_VALUE', () => {
81
+ expect(SafeInt.MIN_VALUE).toBe(Number.MIN_SAFE_INTEGER);
82
+ expect(SafeInt.MAX_VALUE).toBe(Number.MAX_SAFE_INTEGER);
83
+ });
84
+ });
85
+
86
+ describe('mathematical operations', () => {
87
+ const a = asSafeInt(5);
88
+ const b = asSafeInt(2);
89
+ const c = asSafeInt(-3);
90
+
91
+ test('abs', () => {
92
+ expect(SafeInt.abs(a)).toBe(5);
93
+ expect(SafeInt.abs(c)).toBe(3);
94
+ expect(SafeInt.abs(asSafeInt(0))).toBe(0);
95
+ });
96
+
97
+ test('min and max', () => {
98
+ expect(SafeInt.min(a, b)).toBe(2);
99
+ expect(SafeInt.max(a, b)).toBe(5);
100
+ expect(SafeInt.min(a, c)).toBe(-3);
101
+ expect(SafeInt.max(a, c)).toBe(5);
102
+ });
103
+
104
+ test('add (with clamping to safe integer range)', () => {
105
+ const largeValue = asSafeInt(Number.MAX_SAFE_INTEGER - 1);
106
+ const result = SafeInt.add(largeValue, asSafeInt(10));
107
+ expect(result).toBe(Number.MAX_SAFE_INTEGER); // clamped to max
108
+ expect(SafeInt.add(a, b)).toBe(7);
109
+ });
110
+
111
+ test('sub (with clamping to safe integer range)', () => {
112
+ const smallValue = asSafeInt(Number.MIN_SAFE_INTEGER + 1);
113
+ const result = SafeInt.sub(smallValue, asSafeInt(10));
114
+ expect(result).toBe(Number.MIN_SAFE_INTEGER); // clamped to min
115
+ expect(SafeInt.sub(a, b)).toBe(3);
116
+ });
117
+
118
+ test('mul (with clamping to safe integer range)', () => {
119
+ const largeValue = asSafeInt(
120
+ Math.floor(Math.sqrt(Number.MAX_SAFE_INTEGER)),
121
+ );
122
+ const result = SafeInt.mul(largeValue, largeValue);
123
+ expect(result).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
124
+ expect(SafeInt.mul(asSafeInt(10), asSafeInt(5))).toBe(50);
125
+ });
126
+
127
+ test('div (floor division with clamping)', () => {
128
+ expect(SafeInt.div(a, b)).toBe(2);
129
+ expect(SafeInt.div(asSafeInt(7), asSafeInt(3))).toBe(2);
130
+ expect(SafeInt.div(asSafeInt(-7), asSafeInt(3))).toBe(-3);
131
+ });
132
+
133
+ test('pow (with clamping to safe integer range)', () => {
134
+ const result = SafeInt.pow(asSafeInt(1000), asSafeInt(10));
135
+ expect(result).toBe(Number.MAX_SAFE_INTEGER); // clamped to max
136
+ expect(SafeInt.pow(asSafeInt(2), asSafeInt(3))).toBe(8);
137
+ });
138
+ });
139
+
140
+ describe('random', () => {
141
+ test('generates safe integers within specified range', () => {
142
+ const min = -20;
143
+ const max = 20;
144
+
145
+ for (let i = 0; i < 10; i++) {
146
+ const result = SafeInt.random(min, max);
147
+ expect(result).toBeGreaterThanOrEqual(min);
148
+ expect(result).toBeLessThanOrEqual(max);
149
+ expect(SafeInt.is(result)).toBe(true);
150
+ expect(Number.isInteger(result)).toBe(true);
151
+ }
152
+ });
153
+
154
+ test('generates values within safe integer range', () => {
155
+ for (let i = 0; i < 10; i++) {
156
+ const result = SafeInt.random(-30, 30);
157
+ expect(result).toBeGreaterThanOrEqual(Number.MIN_SAFE_INTEGER);
158
+ expect(result).toBeLessThanOrEqual(Number.MAX_SAFE_INTEGER);
159
+ }
160
+ });
161
+ });
162
+
163
+ describe('type assertions', () => {
164
+ test('type relationships', () => {
165
+ expectType<SafeInt, number>('<=');
166
+
167
+ expectTypeOf(asSafeInt(5)).toExtend<SafeInt>();
168
+ });
169
+ });
170
+ });