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,204 @@
1
+ import { expectType } from '../../expect-type.mjs';
2
+ import {
3
+ asPositiveFiniteNumber,
4
+ isPositiveFiniteNumber,
5
+ PositiveFiniteNumber,
6
+ } from './positive-finite-number.mjs';
7
+
8
+ describe('PositiveFiniteNumber', () => {
9
+ describe('asPositiveFiniteNumber', () => {
10
+ test('accepts valid positive finite numbers', () => {
11
+ expect(() => asPositiveFiniteNumber(1)).not.toThrow();
12
+ expect(() => asPositiveFiniteNumber(3.14)).not.toThrow();
13
+ expect(() => asPositiveFiniteNumber(0.5)).not.toThrow();
14
+ expect(() => asPositiveFiniteNumber(Number.MIN_VALUE)).not.toThrow();
15
+ expect(() => asPositiveFiniteNumber(Number.MAX_VALUE)).not.toThrow();
16
+ });
17
+
18
+ test('rejects zero', () => {
19
+ expect(() => asPositiveFiniteNumber(0)).toThrow(TypeError);
20
+ expect(() => asPositiveFiniteNumber(-0)).toThrow(TypeError);
21
+ });
22
+
23
+ test('rejects negative numbers', () => {
24
+ expect(() => asPositiveFiniteNumber(-1)).toThrow(TypeError);
25
+ expect(() => asPositiveFiniteNumber(-0.1)).toThrow(TypeError);
26
+ expect(() => asPositiveFiniteNumber(-Number.MAX_VALUE)).toThrow(
27
+ TypeError,
28
+ );
29
+ });
30
+
31
+ test('rejects non-finite numbers', () => {
32
+ expect(() => asPositiveFiniteNumber(Number.NaN)).toThrow(TypeError);
33
+ expect(() => asPositiveFiniteNumber(Number.POSITIVE_INFINITY)).toThrow(
34
+ TypeError,
35
+ );
36
+ expect(() => asPositiveFiniteNumber(Number.NEGATIVE_INFINITY)).toThrow(
37
+ TypeError,
38
+ );
39
+ });
40
+
41
+ test('returns the same value for valid inputs', () => {
42
+ expect(asPositiveFiniteNumber(5.5)).toBe(5.5);
43
+ expect(asPositiveFiniteNumber(1)).toBe(1);
44
+ expect(asPositiveFiniteNumber(10)).toBe(10);
45
+ });
46
+
47
+ test.each([
48
+ { name: 'Number.NaN', value: Number.NaN },
49
+ { name: 'Number.POSITIVE_INFINITY', value: Number.POSITIVE_INFINITY },
50
+ { name: 'Number.NEGATIVE_INFINITY', value: Number.NEGATIVE_INFINITY },
51
+ { name: '-1.2', value: -1.2 },
52
+ ] as const)(
53
+ `asPositiveFiniteNumber($name) should throw a TypeError`,
54
+ ({ value }) => {
55
+ expect(() => asPositiveFiniteNumber(value)).toThrow(
56
+ new TypeError(`Expected a positive finite number, got: ${value}`),
57
+ );
58
+ },
59
+ );
60
+ });
61
+
62
+ describe('isPositiveFiniteNumber', () => {
63
+ test('correctly identifies positive finite numbers', () => {
64
+ expect(isPositiveFiniteNumber(1)).toBe(true);
65
+ expect(isPositiveFiniteNumber(3.14)).toBe(true);
66
+ expect(isPositiveFiniteNumber(0.5)).toBe(true);
67
+ expect(isPositiveFiniteNumber(Number.MIN_VALUE)).toBe(true);
68
+ expect(isPositiveFiniteNumber(Number.MAX_VALUE)).toBe(true);
69
+ });
70
+
71
+ test('correctly identifies zero', () => {
72
+ expect(isPositiveFiniteNumber(0)).toBe(false);
73
+ expect(isPositiveFiniteNumber(-0)).toBe(false);
74
+ });
75
+
76
+ test('correctly identifies negative numbers', () => {
77
+ expect(isPositiveFiniteNumber(-1)).toBe(false);
78
+ expect(isPositiveFiniteNumber(-0.1)).toBe(false);
79
+ expect(isPositiveFiniteNumber(-Number.MAX_VALUE)).toBe(false);
80
+ });
81
+
82
+ test('correctly identifies non-finite numbers', () => {
83
+ expect(isPositiveFiniteNumber(Number.NaN)).toBe(false);
84
+ expect(isPositiveFiniteNumber(Number.POSITIVE_INFINITY)).toBe(false);
85
+ expect(isPositiveFiniteNumber(Number.NEGATIVE_INFINITY)).toBe(false);
86
+ });
87
+ });
88
+
89
+ describe('PositiveFiniteNumber.is', () => {
90
+ test('same as isPositiveFiniteNumber function', () => {
91
+ expect(PositiveFiniteNumber.is(5)).toBe(isPositiveFiniteNumber(5));
92
+ expect(PositiveFiniteNumber.is(0)).toBe(isPositiveFiniteNumber(0));
93
+ expect(PositiveFiniteNumber.is(-1)).toBe(isPositiveFiniteNumber(-1));
94
+ });
95
+ });
96
+
97
+ describe('constants', () => {
98
+ test('MIN_VALUE', () => {
99
+ expect(PositiveFiniteNumber.MIN_VALUE).toBe(Number.MIN_VALUE);
100
+ });
101
+ });
102
+
103
+ describe('mathematical operations', () => {
104
+ const a = asPositiveFiniteNumber(5.5);
105
+ const b = asPositiveFiniteNumber(2.5);
106
+ const c = asPositiveFiniteNumber(0.5);
107
+
108
+ test('min and max', () => {
109
+ expect(PositiveFiniteNumber.min(a, b)).toBe(2.5);
110
+ expect(PositiveFiniteNumber.max(a, b)).toBe(5.5);
111
+ expect(PositiveFiniteNumber.min(a, c)).toBe(0.5);
112
+ expect(PositiveFiniteNumber.max(a, c)).toBe(5.5);
113
+ });
114
+
115
+ test('floor, ceil, round', () => {
116
+ expect(PositiveFiniteNumber.floor(a)).toBe(5);
117
+ expect(PositiveFiniteNumber.ceil(a)).toBe(6);
118
+ expect(PositiveFiniteNumber.round(a)).toBe(6);
119
+ expect(PositiveFiniteNumber.floor(b)).toBe(2);
120
+ expect(PositiveFiniteNumber.ceil(b)).toBe(3);
121
+ expect(PositiveFiniteNumber.round(b)).toBe(3);
122
+
123
+ // Test edge case with values less than 1
124
+ expect(PositiveFiniteNumber.floor(c)).toBe(0);
125
+ expect(PositiveFiniteNumber.ceil(c)).toBe(1);
126
+ expect(PositiveFiniteNumber.round(c)).toBe(1);
127
+ });
128
+
129
+ test('add (always greater than 0)', () => {
130
+ expect(PositiveFiniteNumber.add(a, b)).toBe(8);
131
+ expect(PositiveFiniteNumber.add(a, c)).toBe(6);
132
+ });
133
+
134
+ test('sub (never goes below Number.MIN_VALUE)', () => {
135
+ const result1 = PositiveFiniteNumber.sub(a, b);
136
+ expect(result1).toBe(3);
137
+
138
+ const result2 = PositiveFiniteNumber.sub(b, a);
139
+ expect(result2).toBe(Number.MIN_VALUE); // clamped to MIN_VALUE
140
+ });
141
+
142
+ test('mul (always greater than 0)', () => {
143
+ expect(PositiveFiniteNumber.mul(a, b)).toBe(13.75);
144
+ expect(PositiveFiniteNumber.mul(a, c)).toBe(2.75);
145
+ });
146
+
147
+ test('div (always greater than 0)', () => {
148
+ expect(PositiveFiniteNumber.div(a, b)).toBe(2.2);
149
+ expect(PositiveFiniteNumber.div(a, asPositiveFiniteNumber(2))).toBe(2.75);
150
+ });
151
+
152
+ test('pow (always greater than 0)', () => {
153
+ expect(
154
+ PositiveFiniteNumber.pow(
155
+ asPositiveFiniteNumber(2),
156
+ asPositiveFiniteNumber(3),
157
+ ),
158
+ ).toBe(8);
159
+ expect(
160
+ PositiveFiniteNumber.pow(
161
+ asPositiveFiniteNumber(3),
162
+ asPositiveFiniteNumber(2),
163
+ ),
164
+ ).toBe(9);
165
+ });
166
+ });
167
+
168
+ describe('random', () => {
169
+ test('generates positive numbers within specified range', () => {
170
+ const min = asPositiveFiniteNumber(1.5);
171
+ const max = asPositiveFiniteNumber(10.3);
172
+
173
+ for (let i = 0; i < 10; i++) {
174
+ const result = PositiveFiniteNumber.random(min, max);
175
+ expect(result).toBeGreaterThanOrEqual(min);
176
+ expect(result).toBeLessThanOrEqual(max);
177
+ expect(PositiveFiniteNumber.is(result)).toBe(true);
178
+ expect(result).toBeGreaterThan(0);
179
+ }
180
+ });
181
+
182
+ test('generates numbers starting from MIN_VALUE', () => {
183
+ const min = asPositiveFiniteNumber(Number.MIN_VALUE);
184
+ const max = asPositiveFiniteNumber(1);
185
+
186
+ for (let i = 0; i < 10; i++) {
187
+ const result = PositiveFiniteNumber.random(min, max);
188
+ expect(result).toBeGreaterThanOrEqual(Number.MIN_VALUE);
189
+ expect(result).toBeLessThanOrEqual(1);
190
+ expect(result).toBeGreaterThan(0);
191
+ }
192
+ });
193
+ });
194
+
195
+ describe('type assertions', () => {
196
+ test('type relationships', () => {
197
+ expectType<PositiveFiniteNumber, number>('<=');
198
+
199
+ expectTypeOf(
200
+ asPositiveFiniteNumber(5.5),
201
+ ).toExtend<PositiveFiniteNumber>();
202
+ });
203
+ });
204
+ });
@@ -0,0 +1,304 @@
1
+ import { expectType } from '../../expect-type.mjs';
2
+ import { TsVerifiedInternals } from '../refined-number-utils.mjs';
3
+
4
+ type ElementType = PositiveInt;
5
+
6
+ const typeNameInMessage = 'a positive integer';
7
+
8
+ const {
9
+ MIN_VALUE,
10
+ min: min_,
11
+ max: max_,
12
+ pow,
13
+ add,
14
+ sub,
15
+ mul,
16
+ div,
17
+ random,
18
+ is,
19
+ castType,
20
+ clamp,
21
+ } = TsVerifiedInternals.RefinedNumberUtils.operatorsForInteger<
22
+ ElementType,
23
+ 1,
24
+ undefined
25
+ >({
26
+ integerOrSafeInteger: 'Integer',
27
+ MIN_VALUE: 1,
28
+ MAX_VALUE: undefined,
29
+ typeNameInMessage,
30
+ } as const);
31
+
32
+ /**
33
+ * Type guard that checks if a value is a positive integer.
34
+ *
35
+ * A positive integer is any integer greater than zero (>= 1).
36
+ * This excludes zero, negative numbers, and non-integers.
37
+ *
38
+ * @param value - The value to check
39
+ * @returns `true` if the value is a positive integer, `false` otherwise
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * isPositiveInt(5); // true
44
+ * isPositiveInt(1); // true
45
+ * isPositiveInt(0); // false (zero is not positive)
46
+ * isPositiveInt(-1); // false (negative)
47
+ * isPositiveInt(5.5); // false (not an integer)
48
+ * isPositiveInt(NaN); // false
49
+ * ```
50
+ */
51
+ export const isPositiveInt = is;
52
+
53
+ /**
54
+ * Casts a number to a PositiveInt branded type.
55
+ *
56
+ * This function validates that the input is a positive integer (>= 1)
57
+ * and returns it with the PositiveInt brand. This ensures type safety
58
+ * for operations that require strictly positive integer values.
59
+ *
60
+ * @param value - The value to cast
61
+ * @returns The value as a PositiveInt branded type
62
+ * @throws {TypeError} If the value is not a positive integer
63
+ *
64
+ * @example
65
+ * ```typescript
66
+ * const count = asPositiveInt(5); // PositiveInt
67
+ * const length = asPositiveInt(100); // PositiveInt
68
+ * const one = asPositiveInt(1); // PositiveInt (minimum valid)
69
+ *
70
+ * // These throw TypeError:
71
+ * // asPositiveInt(0); // Zero is not positive
72
+ * // asPositiveInt(-1); // Negative numbers not allowed
73
+ * // asPositiveInt(5.5); // Not an integer
74
+ * // asPositiveInt(Infinity); // Not finite
75
+ * ```
76
+ */
77
+ export const asPositiveInt = castType;
78
+
79
+ /**
80
+ * Namespace providing type-safe operations for PositiveInt branded types.
81
+ *
82
+ * PositiveInt represents integers that are strictly greater than zero (>= 1).
83
+ * All operations automatically clamp results to maintain the positive constraint,
84
+ * ensuring that arithmetic operations never produce zero or negative values.
85
+ *
86
+ * This type is essential for:
87
+ * - Array lengths and sizes (length >= 1)
88
+ * - Counts and quantities that must be positive
89
+ * - Denominators in division operations
90
+ * - Loop counters and iteration counts
91
+ * - Database primary keys and IDs
92
+ *
93
+ * @example
94
+ * ```typescript
95
+ * // Type validation
96
+ * PositiveInt.is(5); // true
97
+ * PositiveInt.is(1); // true (minimum value)
98
+ * PositiveInt.is(0); // false
99
+ * PositiveInt.is(-1); // false
100
+ *
101
+ * // Automatic clamping in operations
102
+ * const a = asPositiveInt(10);
103
+ * const b = asPositiveInt(3);
104
+ *
105
+ * const sum = PositiveInt.add(a, b); // PositiveInt (13)
106
+ * const diff1 = PositiveInt.sub(a, b); // PositiveInt (7)
107
+ * const diff2 = PositiveInt.sub(b, a); // PositiveInt (1) - clamped!
108
+ * const product = PositiveInt.mul(a, b); // PositiveInt (30)
109
+ * const quotient = PositiveInt.div(a, b); // PositiveInt (3)
110
+ *
111
+ * // Edge case: division that would be < 1
112
+ * const small = PositiveInt.div(asPositiveInt(2), asPositiveInt(3)); // PositiveInt (1)
113
+ *
114
+ * // Range operations
115
+ * const minimum = PositiveInt.min(a, b); // PositiveInt (3)
116
+ * const maximum = PositiveInt.max(a, b); // PositiveInt (10)
117
+ *
118
+ * // Random generation
119
+ * const dice = PositiveInt.random(asPositiveInt(1), asPositiveInt(6)); // 1-6
120
+ * const id = PositiveInt.random(asPositiveInt(1000), asPositiveInt(9999)); // 4-digit ID
121
+ * ```
122
+ */
123
+ export const PositiveInt = {
124
+ /**
125
+ * Type guard that checks if a value is a positive integer.
126
+ *
127
+ * @param value - The value to check
128
+ * @returns `true` if the value is a positive integer, `false` otherwise
129
+ *
130
+ * @see {@link isPositiveInt} for usage examples
131
+ */
132
+ is,
133
+
134
+ /**
135
+ * The minimum value for a PositiveInt.
136
+ * @readonly
137
+ */
138
+ MIN_VALUE,
139
+
140
+ /**
141
+ * Returns the minimum value from a list of positive integers.
142
+ *
143
+ * Since all inputs are guaranteed to be >= 1, the result is also guaranteed
144
+ * to be a positive integer.
145
+ *
146
+ * @param values - The positive integers to compare (at least one required)
147
+ * @returns The smallest value as a PositiveInt
148
+ *
149
+ * @example
150
+ * ```typescript
151
+ * PositiveInt.min(asPositiveInt(5), asPositiveInt(3)); // PositiveInt (3)
152
+ * PositiveInt.min(asPositiveInt(10), asPositiveInt(1), asPositiveInt(7)); // PositiveInt (1)
153
+ * ```
154
+ */
155
+ min: min_,
156
+
157
+ /**
158
+ * Returns the maximum value from a list of positive integers.
159
+ *
160
+ * @param values - The positive integers to compare (at least one required)
161
+ * @returns The largest value as a PositiveInt
162
+ *
163
+ * @example
164
+ * ```typescript
165
+ * PositiveInt.max(asPositiveInt(5), asPositiveInt(3)); // PositiveInt (5)
166
+ * PositiveInt.max(asPositiveInt(10), asPositiveInt(1), asPositiveInt(7)); // PositiveInt (10)
167
+ * ```
168
+ */
169
+ max: max_,
170
+
171
+ /**
172
+ * Clamps a number to the positive integer range.
173
+ *
174
+ * Since PositiveInt has a minimum value of 1, this function ensures
175
+ * that any input less than 1 is clamped to 1.
176
+ *
177
+ * @param value - The number to clamp
178
+ * @returns The value clamped to >= 1 as a PositiveInt
179
+ *
180
+ * @example
181
+ * ```typescript
182
+ * PositiveInt.clamp(5); // PositiveInt (5)
183
+ * PositiveInt.clamp(0); // PositiveInt (1) - clamped to minimum
184
+ * PositiveInt.clamp(-10); // PositiveInt (1) - clamped to minimum
185
+ * PositiveInt.clamp(100); // PositiveInt (100)
186
+ * ```
187
+ */
188
+ clamp,
189
+
190
+ /**
191
+ * Generates a random positive integer within the specified range (inclusive).
192
+ *
193
+ * Both bounds are inclusive, and both min and max must be positive integers.
194
+ * If min > max, they are automatically swapped.
195
+ *
196
+ * @param min - The minimum value (inclusive, must be >= 1)
197
+ * @param max - The maximum value (inclusive, must be >= min)
198
+ * @returns A random PositiveInt in the range [min, max]
199
+ *
200
+ * @example
201
+ * ```typescript
202
+ * // Dice roll
203
+ * const d6 = PositiveInt.random(asPositiveInt(1), asPositiveInt(6));
204
+ *
205
+ * // Random user ID
206
+ * const userId = PositiveInt.random(asPositiveInt(1000), asPositiveInt(9999));
207
+ *
208
+ * // Random page count
209
+ * const pages = PositiveInt.random(asPositiveInt(50), asPositiveInt(500));
210
+ * ```
211
+ */
212
+ random,
213
+
214
+ /**
215
+ * Raises a positive integer to a power, ensuring the result is never less than 1.
216
+ * @param a - The base positive integer
217
+ * @param b - The exponent positive integer
218
+ * @returns `a ** b` as a PositiveInt, but never less than 1
219
+ * @example
220
+ * ```typescript
221
+ * PositiveInt.pow(asPositiveInt(2), asPositiveInt(3)); // PositiveInt (8)
222
+ * ```
223
+ */
224
+ pow,
225
+
226
+ /**
227
+ * Adds two positive integers, ensuring the result is never less than 1.
228
+ * @param a - First positive integer
229
+ * @param b - Second positive integer
230
+ * @returns `a + b` as a PositiveInt, but never less than 1
231
+ * @example
232
+ * ```typescript
233
+ * PositiveInt.add(asPositiveInt(5), asPositiveInt(3)); // PositiveInt (8)
234
+ * ```
235
+ */
236
+ add,
237
+
238
+ /**
239
+ * Subtracts two positive integers, clamping the result to remain positive.
240
+ *
241
+ * If the mathematical result would be <= 0, it is clamped to 1 to maintain
242
+ * the positive integer constraint.
243
+ *
244
+ * @param a - The minuend (positive integer)
245
+ * @param b - The subtrahend (positive integer)
246
+ * @returns `max(1, a - b)` as a PositiveInt
247
+ *
248
+ * @example
249
+ * ```typescript
250
+ * PositiveInt.sub(asPositiveInt(8), asPositiveInt(3)); // PositiveInt (5)
251
+ * PositiveInt.sub(asPositiveInt(3), asPositiveInt(8)); // PositiveInt (1) - clamped
252
+ * PositiveInt.sub(asPositiveInt(5), asPositiveInt(5)); // PositiveInt (1) - clamped
253
+ * ```
254
+ */
255
+ sub,
256
+
257
+ /**
258
+ * Multiplies two positive integers, ensuring the result is never less than 1.
259
+ * @param a - First positive integer
260
+ * @param b - Second positive integer
261
+ * @returns `a * b` as a PositiveInt, but never less than 1
262
+ * @example
263
+ * ```typescript
264
+ * PositiveInt.mul(asPositiveInt(4), asPositiveInt(3)); // PositiveInt (12)
265
+ * ```
266
+ */
267
+ mul,
268
+
269
+ /**
270
+ * Divides two positive integers using floor division, clamping to remain positive.
271
+ *
272
+ * Performs mathematical floor division: `⌊a / b⌋`. If the result would be 0
273
+ * (when a < b), it is clamped to 1 to maintain the positive integer constraint.
274
+ *
275
+ * @param a - The dividend (positive integer)
276
+ * @param b - The divisor (positive integer, guaranteed non-zero)
277
+ * @returns `max(1, ⌊a / b⌋)` as a PositiveInt
278
+ *
279
+ * @example
280
+ * ```typescript
281
+ * PositiveInt.div(asPositiveInt(10), asPositiveInt(3)); // PositiveInt (3)
282
+ * PositiveInt.div(asPositiveInt(9), asPositiveInt(3)); // PositiveInt (3)
283
+ * PositiveInt.div(asPositiveInt(2), asPositiveInt(3)); // PositiveInt (1) - clamped
284
+ * PositiveInt.div(asPositiveInt(1), asPositiveInt(5)); // PositiveInt (1) - clamped
285
+ * ```
286
+ */
287
+ div,
288
+ } as const;
289
+
290
+ expectType<
291
+ keyof typeof PositiveInt,
292
+ keyof TsVerifiedInternals.RefinedNumberUtils.NumberClass<
293
+ ElementType,
294
+ 'int' | 'positive'
295
+ >
296
+ >('=');
297
+
298
+ expectType<
299
+ typeof PositiveInt,
300
+ TsVerifiedInternals.RefinedNumberUtils.NumberClass<
301
+ ElementType,
302
+ 'int' | 'positive'
303
+ >
304
+ >('<=');
@@ -0,0 +1,176 @@
1
+ import { expectType } from '../../expect-type.mjs';
2
+ import { asPositiveInt, isPositiveInt, PositiveInt } from './positive-int.mjs';
3
+
4
+ describe('PositiveInt', () => {
5
+ describe('asPositiveInt', () => {
6
+ test('accepts valid positive integers', () => {
7
+ expect(() => asPositiveInt(1)).not.toThrow();
8
+ expect(() => asPositiveInt(2)).not.toThrow();
9
+ expect(() => asPositiveInt(42)).not.toThrow();
10
+ expect(() => asPositiveInt(100)).not.toThrow();
11
+ expect(() => asPositiveInt(Number.MAX_SAFE_INTEGER)).not.toThrow();
12
+ });
13
+
14
+ test('rejects zero', () => {
15
+ expect(() => asPositiveInt(0)).toThrow(TypeError);
16
+ expect(() => asPositiveInt(-0)).toThrow(TypeError);
17
+ });
18
+
19
+ test('rejects negative integers', () => {
20
+ expect(() => asPositiveInt(-1)).toThrow(TypeError);
21
+ expect(() => asPositiveInt(-42)).toThrow(TypeError);
22
+ expect(() => asPositiveInt(Number.MIN_SAFE_INTEGER)).toThrow(TypeError);
23
+ });
24
+
25
+ test('rejects non-integers', () => {
26
+ expect(() => asPositiveInt(Number.NaN)).toThrow(TypeError);
27
+ expect(() => asPositiveInt(Number.POSITIVE_INFINITY)).toThrow(TypeError);
28
+ expect(() => asPositiveInt(Number.NEGATIVE_INFINITY)).toThrow(TypeError);
29
+ expect(() => asPositiveInt(1.2)).toThrow(TypeError);
30
+ expect(() => asPositiveInt(-3.4)).toThrow(TypeError);
31
+ });
32
+
33
+ test('returns the same value for valid inputs', () => {
34
+ expect(asPositiveInt(5)).toBe(5);
35
+ expect(asPositiveInt(1)).toBe(1);
36
+ expect(asPositiveInt(10)).toBe(10);
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
+ { name: '0', value: 0 },
46
+ { name: '-1', value: -1 },
47
+ ] as const)(
48
+ `asPositiveInt($name) should throw a TypeError`,
49
+ ({ value }) => {
50
+ expect(() => asPositiveInt(value)).toThrow(
51
+ new TypeError(`Expected a positive integer, got: ${value}`),
52
+ );
53
+ },
54
+ );
55
+ });
56
+
57
+ describe('isPositiveInt', () => {
58
+ test('correctly identifies positive integers', () => {
59
+ expect(isPositiveInt(1)).toBe(true);
60
+ expect(isPositiveInt(2)).toBe(true);
61
+ expect(isPositiveInt(42)).toBe(true);
62
+ expect(isPositiveInt(100)).toBe(true);
63
+ expect(isPositiveInt(Number.MAX_SAFE_INTEGER)).toBe(true);
64
+ });
65
+
66
+ test('correctly identifies zero', () => {
67
+ expect(isPositiveInt(0)).toBe(false);
68
+ expect(isPositiveInt(-0)).toBe(false);
69
+ });
70
+
71
+ test('correctly identifies negative integers', () => {
72
+ expect(isPositiveInt(-1)).toBe(false);
73
+ expect(isPositiveInt(-42)).toBe(false);
74
+ expect(isPositiveInt(Number.MIN_SAFE_INTEGER)).toBe(false);
75
+ });
76
+
77
+ test('correctly identifies non-integers', () => {
78
+ expect(isPositiveInt(Number.NaN)).toBe(false);
79
+ expect(isPositiveInt(Number.POSITIVE_INFINITY)).toBe(false);
80
+ expect(isPositiveInt(Number.NEGATIVE_INFINITY)).toBe(false);
81
+ expect(isPositiveInt(1.2)).toBe(false);
82
+ expect(isPositiveInt(-3.4)).toBe(false);
83
+ });
84
+ });
85
+
86
+ describe('PositiveInt.is', () => {
87
+ test('same as isPositiveInt function', () => {
88
+ expect(PositiveInt.is(5)).toBe(isPositiveInt(5));
89
+ expect(PositiveInt.is(0)).toBe(isPositiveInt(0));
90
+ expect(PositiveInt.is(-10)).toBe(isPositiveInt(-10));
91
+ });
92
+ });
93
+
94
+ describe('constants', () => {
95
+ test('MIN_VALUE', () => {
96
+ expect(PositiveInt.MIN_VALUE).toBe(1);
97
+ });
98
+ });
99
+
100
+ describe('mathematical operations', () => {
101
+ const a = asPositiveInt(5);
102
+ const b = asPositiveInt(2);
103
+ const c = asPositiveInt(1);
104
+
105
+ test('min and max', () => {
106
+ expect(PositiveInt.min(a, b)).toBe(2);
107
+ expect(PositiveInt.max(a, b)).toBe(5);
108
+ expect(PositiveInt.min(a, c)).toBe(1);
109
+ expect(PositiveInt.max(a, c)).toBe(5);
110
+ });
111
+
112
+ test('add (never goes below 1)', () => {
113
+ expect(PositiveInt.add(a, b)).toBe(7);
114
+ expect(PositiveInt.add(a, c)).toBe(6);
115
+ });
116
+
117
+ test('sub (never goes below 1)', () => {
118
+ expect(PositiveInt.sub(a, b)).toBe(3);
119
+ expect(PositiveInt.sub(b, a)).toBe(1); // clamped to 1
120
+ expect(PositiveInt.sub(c, a)).toBe(1); // clamped to 1
121
+ });
122
+
123
+ test('mul (never goes below 1)', () => {
124
+ expect(PositiveInt.mul(a, b)).toBe(10);
125
+ expect(PositiveInt.mul(a, c)).toBe(5);
126
+ });
127
+
128
+ test('div (floor division, never goes below 1)', () => {
129
+ expect(PositiveInt.div(a, b)).toBe(2);
130
+ expect(PositiveInt.div(asPositiveInt(7), asPositiveInt(3))).toBe(2);
131
+ expect(PositiveInt.div(b, a)).toBe(1); // floor(2/5) = 0, but clamped to 1
132
+ });
133
+
134
+ test('pow (never goes below 1)', () => {
135
+ expect(PositiveInt.pow(asPositiveInt(2), asPositiveInt(3))).toBe(8);
136
+ expect(PositiveInt.pow(asPositiveInt(3), asPositiveInt(2))).toBe(9);
137
+ expect(PositiveInt.pow(asPositiveInt(5), asPositiveInt(1))).toBe(5);
138
+ });
139
+ });
140
+
141
+ describe('random', () => {
142
+ test('generates positive integers within specified range', () => {
143
+ const min = 1;
144
+ const max = 10;
145
+
146
+ for (let i = 0; i < 10; i++) {
147
+ const result = PositiveInt.random(min, max);
148
+ expect(result).toBeGreaterThanOrEqual(min);
149
+ expect(result).toBeLessThanOrEqual(max);
150
+ expect(PositiveInt.is(result)).toBe(true);
151
+ expect(Number.isInteger(result)).toBe(true);
152
+ expect(result).toBeGreaterThan(0);
153
+ }
154
+ });
155
+
156
+ test('generates integers starting from 1', () => {
157
+ const min = 1;
158
+ const max = 5;
159
+
160
+ for (let i = 0; i < 10; i++) {
161
+ const result = PositiveInt.random(min, max);
162
+ expect(result).toBeGreaterThanOrEqual(1);
163
+ expect(result).toBeLessThanOrEqual(5);
164
+ expect(result).toBeGreaterThan(0);
165
+ }
166
+ });
167
+ });
168
+
169
+ describe('type assertions', () => {
170
+ test('type relationships', () => {
171
+ expectType<PositiveInt, number>('<=');
172
+
173
+ expectTypeOf(asPositiveInt(5)).toExtend<PositiveInt>();
174
+ });
175
+ });
176
+ });