ts-data-forge 6.13.1 → 7.0.1

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 (257) hide show
  1. package/README.md +2 -2
  2. package/dist/array/impl/array-utils-creation.d.mts.map +1 -1
  3. package/dist/array/impl/array-utils-creation.mjs +1 -1
  4. package/dist/array/impl/array-utils-creation.mjs.map +1 -1
  5. package/dist/array/impl/array-utils-iterators.mjs +7 -1
  6. package/dist/array/impl/array-utils-iterators.mjs.map +1 -1
  7. package/dist/array/impl/array-utils-modification.mjs +6 -0
  8. package/dist/array/impl/array-utils-modification.mjs.map +1 -1
  9. package/dist/array/impl/array-utils-reducing-value.d.mts +1 -1
  10. package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
  11. package/dist/array/impl/array-utils-reducing-value.mjs +6 -1
  12. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  13. package/dist/array/impl/array-utils-search.d.mts +0 -2
  14. package/dist/array/impl/array-utils-search.d.mts.map +1 -1
  15. package/dist/array/impl/array-utils-search.mjs +6 -0
  16. package/dist/array/impl/array-utils-search.mjs.map +1 -1
  17. package/dist/array/impl/array-utils-set-op.d.mts.map +1 -1
  18. package/dist/array/impl/array-utils-set-op.mjs +0 -1
  19. package/dist/array/impl/array-utils-set-op.mjs.map +1 -1
  20. package/dist/array/impl/array-utils-slice-clamped.mjs +6 -0
  21. package/dist/array/impl/array-utils-slice-clamped.mjs.map +1 -1
  22. package/dist/array/impl/array-utils-slicing.mjs +6 -0
  23. package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
  24. package/dist/array/impl/array-utils-transformation.d.mts +1 -1
  25. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  26. package/dist/array/impl/array-utils-transformation.mjs +8 -4
  27. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  28. package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
  29. package/dist/array/impl/array-utils-validation.mjs +9 -9
  30. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  31. package/dist/collections/imap-mapped.d.mts +622 -3
  32. package/dist/collections/imap-mapped.d.mts.map +1 -1
  33. package/dist/collections/imap-mapped.mjs +117 -18
  34. package/dist/collections/imap-mapped.mjs.map +1 -1
  35. package/dist/collections/imap.d.mts +3 -3
  36. package/dist/collections/imap.d.mts.map +1 -1
  37. package/dist/collections/imap.mjs +12 -5
  38. package/dist/collections/imap.mjs.map +1 -1
  39. package/dist/collections/iset-mapped.d.mts +4 -4
  40. package/dist/collections/iset-mapped.d.mts.map +1 -1
  41. package/dist/collections/iset-mapped.mjs +14 -4
  42. package/dist/collections/iset-mapped.mjs.map +1 -1
  43. package/dist/collections/iset.d.mts +2 -2
  44. package/dist/collections/iset.d.mts.map +1 -1
  45. package/dist/collections/iset.mjs +11 -2
  46. package/dist/collections/iset.mjs.map +1 -1
  47. package/dist/collections/queue.mjs +26 -20
  48. package/dist/collections/queue.mjs.map +1 -1
  49. package/dist/collections/stack.mjs +23 -17
  50. package/dist/collections/stack.mjs.map +1 -1
  51. package/dist/entry-point.mjs +6 -0
  52. package/dist/entry-point.mjs.map +1 -1
  53. package/dist/functional/optional/impl/optional-flat-map.d.mts +2 -2
  54. package/dist/functional/pipe.d.mts.map +1 -1
  55. package/dist/functional/pipe.mjs.map +1 -1
  56. package/dist/functional/result/impl/result-flat-map.d.mts +2 -2
  57. package/dist/functional/ternary-result/impl/ternary-result-flat-map.d.mts +5 -3
  58. package/dist/functional/ternary-result/impl/ternary-result-flat-map.d.mts.map +1 -1
  59. package/dist/functional/ternary-result/impl/ternary-result-flat-map.mjs.map +1 -1
  60. package/dist/guard/has-key.d.mts.map +1 -1
  61. package/dist/guard/has-key.mjs +1 -3
  62. package/dist/guard/has-key.mjs.map +1 -1
  63. package/dist/guard/is-non-null-object.d.mts.map +1 -1
  64. package/dist/guard/is-non-null-object.mjs +1 -3
  65. package/dist/guard/is-non-null-object.mjs.map +1 -1
  66. package/dist/guard/key-is-in.d.mts.map +1 -1
  67. package/dist/guard/key-is-in.mjs +1 -3
  68. package/dist/guard/key-is-in.mjs.map +1 -1
  69. package/dist/index.mjs +6 -0
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/iterator/range.mjs +6 -0
  72. package/dist/iterator/range.mjs.map +1 -1
  73. package/dist/json/json.mjs +11 -7
  74. package/dist/json/json.mjs.map +1 -1
  75. package/dist/number/branded-types/finite-number.d.mts +1 -1
  76. package/dist/number/branded-types/index.d.mts +6 -0
  77. package/dist/number/branded-types/index.d.mts.map +1 -1
  78. package/dist/number/branded-types/index.mjs +6 -0
  79. package/dist/number/branded-types/index.mjs.map +1 -1
  80. package/dist/number/branded-types/int.d.mts +1 -2
  81. package/dist/number/branded-types/int.d.mts.map +1 -1
  82. package/dist/number/branded-types/int16.d.mts +2 -3
  83. package/dist/number/branded-types/int16.d.mts.map +1 -1
  84. package/dist/number/branded-types/int32.d.mts +2 -3
  85. package/dist/number/branded-types/int32.d.mts.map +1 -1
  86. package/dist/number/branded-types/negative-finite-number.d.mts +143 -0
  87. package/dist/number/branded-types/negative-finite-number.d.mts.map +1 -0
  88. package/dist/number/branded-types/negative-finite-number.mjs +156 -0
  89. package/dist/number/branded-types/negative-finite-number.mjs.map +1 -0
  90. package/dist/number/branded-types/negative-int.d.mts +120 -0
  91. package/dist/number/branded-types/negative-int.d.mts.map +1 -0
  92. package/dist/number/branded-types/negative-int.mjs +125 -0
  93. package/dist/number/branded-types/negative-int.mjs.map +1 -0
  94. package/dist/number/branded-types/negative-safe-int.d.mts +132 -0
  95. package/dist/number/branded-types/negative-safe-int.d.mts.map +1 -0
  96. package/dist/number/branded-types/negative-safe-int.mjs +136 -0
  97. package/dist/number/branded-types/negative-safe-int.mjs.map +1 -0
  98. package/dist/number/branded-types/non-negative-finite-number.d.mts +1 -1
  99. package/dist/number/branded-types/non-negative-int16.d.mts +1 -1
  100. package/dist/number/branded-types/non-negative-int32.d.mts +1 -1
  101. package/dist/number/branded-types/non-positive-finite-number.d.mts +130 -0
  102. package/dist/number/branded-types/non-positive-finite-number.d.mts.map +1 -0
  103. package/dist/number/branded-types/non-positive-finite-number.mjs +134 -0
  104. package/dist/number/branded-types/non-positive-finite-number.mjs.map +1 -0
  105. package/dist/number/branded-types/non-positive-int.d.mts +224 -0
  106. package/dist/number/branded-types/non-positive-int.d.mts.map +1 -0
  107. package/dist/number/branded-types/non-positive-int.mjs +222 -0
  108. package/dist/number/branded-types/non-positive-int.mjs.map +1 -0
  109. package/dist/number/branded-types/non-positive-safe-int.d.mts +119 -0
  110. package/dist/number/branded-types/non-positive-safe-int.d.mts.map +1 -0
  111. package/dist/number/branded-types/non-positive-safe-int.mjs +116 -0
  112. package/dist/number/branded-types/non-positive-safe-int.mjs.map +1 -0
  113. package/dist/number/branded-types/non-zero-finite-number.d.mts +5 -17
  114. package/dist/number/branded-types/non-zero-finite-number.d.mts.map +1 -1
  115. package/dist/number/branded-types/non-zero-finite-number.mjs +5 -17
  116. package/dist/number/branded-types/non-zero-finite-number.mjs.map +1 -1
  117. package/dist/number/branded-types/non-zero-int.d.mts +5 -33
  118. package/dist/number/branded-types/non-zero-int.d.mts.map +1 -1
  119. package/dist/number/branded-types/non-zero-int.mjs +5 -25
  120. package/dist/number/branded-types/non-zero-int.mjs.map +1 -1
  121. package/dist/number/branded-types/non-zero-int16.d.mts +6 -43
  122. package/dist/number/branded-types/non-zero-int16.d.mts.map +1 -1
  123. package/dist/number/branded-types/non-zero-int16.mjs +5 -25
  124. package/dist/number/branded-types/non-zero-int16.mjs.map +1 -1
  125. package/dist/number/branded-types/non-zero-int32.d.mts +6 -39
  126. package/dist/number/branded-types/non-zero-int32.d.mts.map +1 -1
  127. package/dist/number/branded-types/non-zero-int32.mjs +5 -25
  128. package/dist/number/branded-types/non-zero-int32.mjs.map +1 -1
  129. package/dist/number/branded-types/non-zero-safe-int.d.mts +5 -37
  130. package/dist/number/branded-types/non-zero-safe-int.d.mts.map +1 -1
  131. package/dist/number/branded-types/non-zero-safe-int.mjs +7 -30
  132. package/dist/number/branded-types/non-zero-safe-int.mjs.map +1 -1
  133. package/dist/number/branded-types/non-zero-uint16.d.mts +6 -33
  134. package/dist/number/branded-types/non-zero-uint16.d.mts.map +1 -1
  135. package/dist/number/branded-types/non-zero-uint16.mjs +5 -17
  136. package/dist/number/branded-types/non-zero-uint16.mjs.map +1 -1
  137. package/dist/number/branded-types/non-zero-uint32.d.mts +6 -31
  138. package/dist/number/branded-types/non-zero-uint32.d.mts.map +1 -1
  139. package/dist/number/branded-types/non-zero-uint32.mjs +5 -17
  140. package/dist/number/branded-types/non-zero-uint32.mjs.map +1 -1
  141. package/dist/number/branded-types/positive-finite-number.d.mts +4 -2
  142. package/dist/number/branded-types/positive-finite-number.d.mts.map +1 -1
  143. package/dist/number/branded-types/positive-int.d.mts +4 -2
  144. package/dist/number/branded-types/positive-int.d.mts.map +1 -1
  145. package/dist/number/branded-types/positive-int16.d.mts +4 -2
  146. package/dist/number/branded-types/positive-int16.d.mts.map +1 -1
  147. package/dist/number/branded-types/positive-int32.d.mts +4 -2
  148. package/dist/number/branded-types/positive-int32.d.mts.map +1 -1
  149. package/dist/number/branded-types/positive-safe-int.d.mts +4 -2
  150. package/dist/number/branded-types/positive-safe-int.d.mts.map +1 -1
  151. package/dist/number/branded-types/positive-safe-int.mjs +1 -1
  152. package/dist/number/branded-types/positive-uint16.d.mts +4 -2
  153. package/dist/number/branded-types/positive-uint16.d.mts.map +1 -1
  154. package/dist/number/branded-types/positive-uint32.d.mts +4 -2
  155. package/dist/number/branded-types/positive-uint32.d.mts.map +1 -1
  156. package/dist/number/branded-types/safe-int.d.mts +1 -2
  157. package/dist/number/branded-types/safe-int.d.mts.map +1 -1
  158. package/dist/number/branded-types/safe-int.mjs +2 -2
  159. package/dist/number/branded-types/safe-uint.d.mts +1 -1
  160. package/dist/number/branded-types/safe-uint.mjs +1 -1
  161. package/dist/number/branded-types/uint.d.mts +1 -1
  162. package/dist/number/branded-types/uint16.d.mts +1 -1
  163. package/dist/number/branded-types/uint32.d.mts +1 -1
  164. package/dist/number/index.d.mts +1 -0
  165. package/dist/number/index.d.mts.map +1 -1
  166. package/dist/number/index.mjs +6 -0
  167. package/dist/number/index.mjs.map +1 -1
  168. package/dist/number/num-arithmetic-types.d.mts +74 -0
  169. package/dist/number/num-arithmetic-types.d.mts.map +1 -0
  170. package/dist/number/num-arithmetic-types.mjs +2 -0
  171. package/dist/number/num-arithmetic-types.mjs.map +1 -0
  172. package/dist/number/num.d.mts +53 -9
  173. package/dist/number/num.d.mts.map +1 -1
  174. package/dist/number/num.mjs +67 -15
  175. package/dist/number/num.mjs.map +1 -1
  176. package/dist/number/refined-number-utils.d.mts +12 -9
  177. package/dist/number/refined-number-utils.d.mts.map +1 -1
  178. package/dist/number/refined-number-utils.mjs.map +1 -1
  179. package/dist/object/object.mjs +7 -1
  180. package/dist/object/object.mjs.map +1 -1
  181. package/dist/others/fast-deep-equal.d.mts.map +1 -1
  182. package/dist/others/fast-deep-equal.mjs +0 -1
  183. package/dist/others/fast-deep-equal.mjs.map +1 -1
  184. package/dist/others/unknown-to-string.mjs +2 -2
  185. package/dist/others/unknown-to-string.mjs.map +1 -1
  186. package/package.json +6 -6
  187. package/src/array/impl/array-utils-creation.mts +1 -1
  188. package/src/array/impl/array-utils-iterators.mts +1 -1
  189. package/src/array/impl/array-utils-reducing-value.mts +1 -2
  190. package/src/array/impl/array-utils-search.mts +0 -2
  191. package/src/array/impl/array-utils-set-op.mts +0 -1
  192. package/src/array/impl/array-utils-transformation.mts +8 -6
  193. package/src/array/impl/array-utils-validation.mts +1 -5
  194. package/src/collections/imap-mapped.mts +696 -34
  195. package/src/collections/imap-mapped.test.mts +61 -13
  196. package/src/collections/imap.mts +20 -9
  197. package/src/collections/imap.test.mts +9 -8
  198. package/src/collections/iset-mapped.mts +18 -10
  199. package/src/collections/iset-mapped.test.mts +6 -1
  200. package/src/collections/iset.mts +11 -4
  201. package/src/collections/iset.test.mts +6 -8
  202. package/src/collections/queue.mts +32 -32
  203. package/src/collections/stack.mts +26 -26
  204. package/src/functional/optional/impl/optional-flat-map.mts +2 -2
  205. package/src/functional/optional.test.mts +6 -4
  206. package/src/functional/pipe.mts +0 -1
  207. package/src/functional/pipe.test.mts +2 -1
  208. package/src/functional/result/impl/result-flat-map.mts +2 -2
  209. package/src/functional/result.test.mts +3 -2
  210. package/src/functional/ternary-result/impl/ternary-result-flat-map.mts +5 -3
  211. package/src/functional/ternary-result.test.mts +8 -3
  212. package/src/guard/has-key.mts +1 -3
  213. package/src/guard/is-non-null-object.mts +0 -1
  214. package/src/guard/key-is-in.mts +1 -3
  215. package/src/json/json.mts +5 -8
  216. package/src/json/json.test.mts +2 -2
  217. package/src/number/branded-types/index.mts +6 -0
  218. package/src/number/branded-types/negative-finite-number.mts +210 -0
  219. package/src/number/branded-types/negative-finite-number.test.mts +172 -0
  220. package/src/number/branded-types/negative-int.mts +160 -0
  221. package/src/number/branded-types/negative-int.test.mts +146 -0
  222. package/src/number/branded-types/negative-safe-int.mts +173 -0
  223. package/src/number/branded-types/negative-safe-int.test.mts +120 -0
  224. package/src/number/branded-types/non-positive-finite-number.mts +191 -0
  225. package/src/number/branded-types/non-positive-finite-number.test.mts +286 -0
  226. package/src/number/branded-types/non-positive-int.mts +251 -0
  227. package/src/number/branded-types/non-positive-int.test.mts +197 -0
  228. package/src/number/branded-types/non-positive-safe-int.mts +147 -0
  229. package/src/number/branded-types/non-positive-safe-int.test.mts +223 -0
  230. package/src/number/branded-types/non-zero-finite-number.mts +14 -22
  231. package/src/number/branded-types/non-zero-finite-number.test.mts +0 -12
  232. package/src/number/branded-types/non-zero-int.mts +10 -32
  233. package/src/number/branded-types/non-zero-int.test.mts +0 -20
  234. package/src/number/branded-types/non-zero-int16.mts +8 -32
  235. package/src/number/branded-types/non-zero-int16.test.mts +0 -30
  236. package/src/number/branded-types/non-zero-int32.mts +8 -32
  237. package/src/number/branded-types/non-zero-int32.test.mts +0 -30
  238. package/src/number/branded-types/non-zero-safe-int.mts +10 -37
  239. package/src/number/branded-types/non-zero-safe-int.test.mts +0 -32
  240. package/src/number/branded-types/non-zero-uint16.mts +8 -22
  241. package/src/number/branded-types/non-zero-uint16.test.mts +0 -16
  242. package/src/number/branded-types/non-zero-uint32.mts +8 -22
  243. package/src/number/branded-types/non-zero-uint32.test.mts +0 -16
  244. package/src/number/branded-types/positive-safe-int.mts +1 -1
  245. package/src/number/branded-types/safe-int.mts +2 -2
  246. package/src/number/branded-types/safe-uint.mts +1 -1
  247. package/src/number/index.mts +1 -0
  248. package/src/number/num-arithmetic-types.mts +265 -0
  249. package/src/number/num-arithmetic-types.test.mts +130 -0
  250. package/src/number/num-arithmetic.test.mts +102 -0
  251. package/src/number/num.mts +99 -18
  252. package/src/number/num.test.mts +2 -2
  253. package/src/number/refined-number-utils.mts +19 -9
  254. package/src/object/object.mts +2 -2
  255. package/src/others/fast-deep-equal.mts +0 -2
  256. package/src/others/map-nullable.test.mts +4 -3
  257. package/src/others/unknown-to-string.mts +2 -2
@@ -0,0 +1,130 @@
1
+ import {
2
+ type FiniteNumber,
3
+ type Int,
4
+ type NegativeFiniteNumber,
5
+ type NegativeInt,
6
+ type NonNegativeFiniteNumber,
7
+ type NonPositiveInt,
8
+ type NonZeroInt,
9
+ type PositiveFiniteNumber,
10
+ type PositiveInt,
11
+ type PositiveSafeInt,
12
+ type SafeUint,
13
+ type Uint,
14
+ } from 'ts-type-forge';
15
+ import { expectType } from '../expect-type.mjs';
16
+ import {
17
+ type AddResult,
18
+ type DivIntResult,
19
+ type DivResult,
20
+ type LevelOf,
21
+ type MulResult,
22
+ type SignOf,
23
+ type SubResult,
24
+ } from './num-arithmetic-types.mjs';
25
+
26
+ describe('NumericArithmetic type-level', () => {
27
+ test('SignOf', () => {
28
+ expectType<SignOf<PositiveInt>, 'pos'>('=');
29
+
30
+ expectType<SignOf<NegativeInt>, 'neg'>('=');
31
+
32
+ expectType<SignOf<Uint>, 'nonneg'>('=');
33
+
34
+ expectType<SignOf<NonPositiveInt>, 'nonpos'>('=');
35
+
36
+ expectType<SignOf<NonZeroInt>, 'nonzero'>('=');
37
+
38
+ expectType<SignOf<Int>, 'any'>('=');
39
+
40
+ expectType<SignOf<number>, 'any'>('=');
41
+ });
42
+
43
+ test('LevelOf', () => {
44
+ expectType<LevelOf<PositiveSafeInt>, 'safeint'>('=');
45
+
46
+ expectType<LevelOf<PositiveInt>, 'int'>('=');
47
+
48
+ expectType<LevelOf<PositiveFiniteNumber>, 'finite'>('=');
49
+
50
+ expectType<LevelOf<number>, 'number'>('=');
51
+ });
52
+
53
+ test('mul: sign flips', () => {
54
+ // <=0 * <=0 = >=0
55
+ expectType<MulResult<NonPositiveInt, NonPositiveInt>, Uint>('=');
56
+
57
+ // <0 * <0 = >0
58
+ expectType<MulResult<NegativeInt, NegativeInt>, PositiveInt>('=');
59
+
60
+ // >0 * <0 = <0
61
+ expectType<MulResult<PositiveInt, NegativeInt>, NegativeInt>('=');
62
+
63
+ // finite negatives -> positive finite
64
+ expectType<
65
+ MulResult<NegativeFiniteNumber, NegativeFiniteNumber>,
66
+ PositiveFiniteNumber
67
+ >('=');
68
+ });
69
+
70
+ test('mul: safeint widens to int', () => {
71
+ expectType<MulResult<PositiveSafeInt, PositiveSafeInt>, PositiveInt>('=');
72
+ });
73
+
74
+ test('add', () => {
75
+ expectType<AddResult<PositiveInt, PositiveInt>, PositiveInt>('=');
76
+
77
+ expectType<AddResult<Uint, Uint>, Uint>('=');
78
+
79
+ // positive + negative -> unknown sign
80
+ expectType<AddResult<PositiveInt, NegativeInt>, Int>('=');
81
+
82
+ // nonzero + nonzero -> can be 0
83
+ expectType<AddResult<NonZeroInt, NonZeroInt>, Int>('=');
84
+
85
+ // safeint widens
86
+ expectType<AddResult<SafeUint, SafeUint>, Uint>('=');
87
+ });
88
+
89
+ test('sub', () => {
90
+ // same-sign sub -> unknown sign (the saturating case lives in branded)
91
+ expectType<SubResult<Uint, Uint>, Int>('=');
92
+
93
+ expectType<SubResult<NonPositiveInt, NonPositiveInt>, Int>('=');
94
+
95
+ // >=0 - <=0 = >=0
96
+ expectType<SubResult<Uint, NonPositiveInt>, Uint>('=');
97
+ });
98
+
99
+ test('div (exact) -> finite level', () => {
100
+ expectType<DivResult<PositiveInt, PositiveInt>, PositiveFiniteNumber>('=');
101
+
102
+ expectType<
103
+ DivResult<NegativeFiniteNumber, NegativeFiniteNumber>,
104
+ PositiveFiniteNumber
105
+ >('=');
106
+
107
+ expectType<DivResult<NonPositiveInt, NegativeInt>, NonNegativeFiniteNumber>(
108
+ '=',
109
+ );
110
+ });
111
+
112
+ test('divInt (floor)', () => {
113
+ // <0 / <0 = >0 but floor can be 0 -> Uint
114
+ expectType<DivIntResult<NegativeInt, NegativeInt>, Uint>('=');
115
+
116
+ // >0 / >0 floor can be 0 -> Uint
117
+ expectType<DivIntResult<PositiveInt, PositiveInt>, Uint>('=');
118
+
119
+ // safeint preserved under floor division
120
+ expectType<DivIntResult<PositiveSafeInt, PositiveSafeInt>, SafeUint>('=');
121
+ });
122
+
123
+ test('plain number stays number', () => {
124
+ expectType<AddResult<number, number>, number>('=');
125
+
126
+ expectType<MulResult<number, PositiveInt>, number>('=');
127
+
128
+ expectType<AddResult<FiniteNumber, FiniteNumber>, FiniteNumber>('=');
129
+ });
130
+ });
@@ -0,0 +1,102 @@
1
+ import {
2
+ type Int,
3
+ type NegativeInt,
4
+ type PositiveFiniteNumber,
5
+ type PositiveInt,
6
+ type Uint,
7
+ } from 'ts-type-forge';
8
+ import { expectType } from '../expect-type.mjs';
9
+ import {
10
+ asNegativeInt,
11
+ asNonPositiveInt,
12
+ asPositiveInt,
13
+ asUint,
14
+ } from './branded-types/index.mjs';
15
+ import { Num } from './num.mjs';
16
+
17
+ describe('Num arithmetic (general / sign-aware)', () => {
18
+ describe('Num.add', () => {
19
+ test('adds and keeps the value (no saturation)', () => {
20
+ expect(Num.add(asPositiveInt(4), asPositiveInt(5))).toBe(9);
21
+ });
22
+
23
+ test('positive + negative is typed Int', () => {
24
+ const r = Num.add(asPositiveInt(4), asNegativeInt(-9));
25
+
26
+ expect(r).toBe(-5);
27
+
28
+ expectType<typeof r, Int>('=');
29
+ });
30
+
31
+ test('positive + positive stays PositiveInt', () => {
32
+ expectType<
33
+ ReturnType<typeof Num.add<PositiveInt, PositiveInt>>,
34
+ PositiveInt
35
+ >('=');
36
+ });
37
+ });
38
+
39
+ describe('Num.sub', () => {
40
+ test('does not saturate (unlike branded Uint.sub)', () => {
41
+ const r = Num.sub(asUint(3), asUint(5));
42
+
43
+ expect(r).toBe(-2);
44
+
45
+ expectType<typeof r, Int>('=');
46
+ });
47
+ });
48
+
49
+ describe('Num.mul', () => {
50
+ test('negative * negative is positive (PositiveInt)', () => {
51
+ const r = Num.mul(asNegativeInt(-5), asNegativeInt(-3));
52
+
53
+ expect(r).toBe(15);
54
+
55
+ expectType<typeof r, PositiveInt>('=');
56
+ });
57
+
58
+ test('positive * negative is negative (NegativeInt)', () => {
59
+ const r = Num.mul(asPositiveInt(5), asNegativeInt(-3));
60
+
61
+ expect(r).toBe(-15);
62
+
63
+ expectType<typeof r, NegativeInt>('=');
64
+ });
65
+
66
+ test('non-positive * non-positive is non-negative (Uint)', () => {
67
+ const r = Num.mul(asNonPositiveInt(-4), asNonPositiveInt(-2));
68
+
69
+ expect(r).toBe(8);
70
+
71
+ expectType<typeof r, Uint>('=');
72
+ });
73
+ });
74
+
75
+ describe('Num.div (exact)', () => {
76
+ test('exact division keeps the fractional value', () => {
77
+ const r = Num.div(asPositiveInt(9), asPositiveInt(2));
78
+
79
+ expect(r).toBe(4.5);
80
+
81
+ expectType<typeof r, PositiveFiniteNumber>('=');
82
+ });
83
+
84
+ test('negative / negative is positive', () => {
85
+ expect(Num.div(asNegativeInt(-10), asNegativeInt(-2))).toBe(5);
86
+ });
87
+ });
88
+
89
+ describe('Num.divInt (floor)', () => {
90
+ test('floor division of negatives is non-negative (Uint)', () => {
91
+ const r = Num.divInt(asNegativeInt(-7), asNegativeInt(-2));
92
+
93
+ expect(r).toBe(3);
94
+
95
+ expectType<typeof r, Uint>('=');
96
+ });
97
+
98
+ test('floor can reach 0', () => {
99
+ expect(Num.divInt(asNegativeInt(-2), asNegativeInt(-3))).toBe(0);
100
+ });
101
+ });
102
+ });
@@ -19,6 +19,13 @@ import {
19
19
  import { expectType } from '../expect-type.mjs';
20
20
  import { Result } from '../functional/index.mjs';
21
21
  import { type SmallPositiveInt } from '../types.mjs';
22
+ import {
23
+ type AddResult,
24
+ type DivIntResult,
25
+ type DivResult,
26
+ type MulResult,
27
+ type SubResult,
28
+ } from './num-arithmetic-types.mjs';
22
29
 
23
30
  /**
24
31
  * Namespace providing utility functions for number manipulation and validation.
@@ -127,7 +134,7 @@ export namespace Num {
127
134
  ? Result.err(
128
135
  new Error(`safeParseInt: "${s}" is not a valid base-10 integer`),
129
136
  )
130
- : // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
137
+ : // eslint-disable-next-line total-functions/no-unsafe-type-assertion
131
138
  Result.ok(Math.trunc(viaNumber) as Int);
132
139
  };
133
140
 
@@ -207,7 +214,7 @@ export namespace Num {
207
214
  ? Result.err(
208
215
  new Error(`safeParseFloat: "${s}" is not a valid finite number`),
209
216
  )
210
- : // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
217
+ : // eslint-disable-next-line total-functions/no-unsafe-type-assertion
211
218
  Result.ok(viaNumber as FiniteNumber);
212
219
  };
213
220
 
@@ -504,19 +511,86 @@ export namespace Num {
504
511
  }
505
512
 
506
513
  /**
507
- * Performs type-safe division with compile-time zero-check.
514
+ * Adds two numbers, returning the tightest sound branded result type.
515
+ *
516
+ * The sign of the result is computed exactly (e.g. a non-negative plus a
517
+ * non-negative is non-negative); the integer/finite level is preserved, with
518
+ * `SafeInt` operands widening to `Int` because the sum may exceed the safe
519
+ * range. No clamping is performed — for a result that stays inside a bounded
520
+ * type, use that type's branded `add` instead.
521
+ *
522
+ * @param a - The first addend.
523
+ * @param b - The second addend.
524
+ * @returns `a + b`, typed as the tightest branded type guaranteed to contain
525
+ * it.
526
+ */
527
+ export const add = <A extends number, B extends number>(
528
+ a: A,
529
+ b: B,
530
+ ): AddResult<A, B> =>
531
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
532
+ (a + b) as AddResult<A, B>;
533
+
534
+ /**
535
+ * Subtracts `b` from `a`, returning the tightest sound branded result type.
508
536
  *
509
- * This function leverages TypeScript's type system to prevent division by
510
- * zero at compile time. The divisor must be typed as NonZeroNumber or a
511
- * non-zero numeric literal.
537
+ * The sign is computed exactly; the level is preserved (with `SafeInt`
538
+ * widening to `Int`). No clamping — for saturating subtraction that stays in a
539
+ * bounded type (e.g. `Uint.sub` clamping at `0`), use that type's branded
540
+ * `sub`.
512
541
  *
513
- * @param a - The dividend
514
- * @param b - The divisor (must be non-zero, enforced by types)
515
- * @returns The quotient of a / b
542
+ * @param a - The minuend.
543
+ * @param b - The subtrahend.
544
+ * @returns `a - b`, typed as the tightest branded type guaranteed to contain
545
+ * it.
516
546
  */
517
- export const div = (a: number, b: NonZeroNumber | SmallInt<'!=0'>): number =>
518
- // eslint-disable-next-line total-functions/no-partial-division
519
- a / b;
547
+ export const sub = <A extends number, B extends number>(
548
+ a: A,
549
+ b: B,
550
+ ): SubResult<A, B> =>
551
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
552
+ (a - b) as SubResult<A, B>;
553
+
554
+ /**
555
+ * Multiplies two numbers, returning the tightest sound branded result type.
556
+ *
557
+ * The sign follows the usual product rule (e.g. negative times negative is
558
+ * positive); the level is preserved (with `SafeInt` widening to `Int`). No
559
+ * clamping.
560
+ *
561
+ * @param a - The first factor.
562
+ * @param b - The second factor.
563
+ * @returns `a * b`, typed as the tightest branded type guaranteed to contain
564
+ * it.
565
+ */
566
+ export const mul = <A extends number, B extends number>(
567
+ a: A,
568
+ b: B,
569
+ ): MulResult<A, B> =>
570
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
571
+ (a * b) as MulResult<A, B>;
572
+
573
+ /**
574
+ * Performs type-safe exact division with a compile-time zero-check.
575
+ *
576
+ * The divisor must be typed as `NonZeroNumber` or a non-zero numeric literal.
577
+ * The result sign follows the quotient rule; because exact division can be
578
+ * fractional, the level is `finite` (or `number`).
579
+ *
580
+ * @param a - The dividend.
581
+ * @param b - The divisor (must be non-zero, enforced by types).
582
+ * @returns `a / b`, typed as the tightest branded type guaranteed to contain
583
+ * it.
584
+ */
585
+ export const div = <
586
+ A extends number,
587
+ B extends NonZeroNumber | SmallInt<'!=0'>,
588
+ >(
589
+ a: A,
590
+ b: B,
591
+ ): DivResult<A, B> =>
592
+ // eslint-disable-next-line total-functions/no-partial-division, total-functions/no-unsafe-type-assertion
593
+ (a / b) as DivResult<A, B>;
520
594
 
521
595
  /**
522
596
  * Performs integer division using floor division.
@@ -531,12 +605,19 @@ export namespace Num {
531
605
  * @param b - The divisor
532
606
  * @returns The integer quotient, or `NaN` if b is zero
533
607
  */
534
- export const divInt = (
535
- a: number,
536
- b: NonZeroNumber | SmallInt<'!=0'>,
537
- ): number =>
608
+ export const divInt = <
609
+ A extends number,
610
+ B extends NonZeroNumber | SmallInt<'!=0'>,
611
+ >(
612
+ a: A,
613
+ b: B,
614
+ ): DivIntResult<A, B> => {
538
615
  // eslint-disable-next-line total-functions/no-partial-division
539
- Math.floor(Math.floor(a) / Math.floor(b));
616
+ const quotient = Math.floor(Math.floor(a) / Math.floor(b));
617
+
618
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
619
+ return quotient as DivIntResult<A, B>;
620
+ };
540
621
 
541
622
  /**
542
623
  * Rounds a number to a specified number of decimal places.
@@ -568,7 +649,7 @@ export namespace Num {
568
649
  * @param num - The number to round
569
650
  * @returns The rounded integer as an Int branded type
570
651
  */
571
- // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
652
+ // eslint-disable-next-line total-functions/no-unsafe-type-assertion
572
653
  export const roundToInt = (num: number): Int => Math.trunc(num + 0.5) as Int;
573
654
 
574
655
  /**
@@ -164,7 +164,7 @@ describe('Num test', () => {
164
164
  test('rejects empty / whitespace-only input (unlike Number)', () => {
165
165
  assert.isTrue(Result.isErr(Num.safeParseInt('')));
166
166
 
167
- assert.isTrue(Result.isErr(Num.safeParseInt(' ')));
167
+ assert.isTrue(Result.isErr(Num.safeParseInt(' '.repeat(3))));
168
168
  });
169
169
 
170
170
  test('rejects non-finite words', () => {
@@ -222,7 +222,7 @@ describe('Num test', () => {
222
222
  test('rejects empty / whitespace-only input (unlike Number)', () => {
223
223
  assert.isTrue(Result.isErr(Num.safeParseFloat('')));
224
224
 
225
- assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
225
+ assert.isTrue(Result.isErr(Num.safeParseFloat(' '.repeat(3))));
226
226
  });
227
227
 
228
228
  test('rejects non-finite values', () => {
@@ -6,6 +6,7 @@ import {
6
6
  type IntersectBrand,
7
7
  type NonNegativeNumber,
8
8
  type NonZeroNumber,
9
+ type PositiveNumber,
9
10
  type RelaxedExclude,
10
11
  type TypeEq,
11
12
  type UnknownBrand,
@@ -84,10 +85,10 @@ export namespace TsDataForgeInternals {
84
85
  * @template N - A branded number type
85
86
  * @internal
86
87
  */
87
- export type ToNonNegative<N extends UnknownNumberBrand> = IntersectBrand<
88
- N,
89
- NonNegativeNumber
90
- >;
88
+ export type ToNonNegative<N extends UnknownNumberBrand> =
89
+ '!=0' extends UnwrapBrandTrueKeys<N>
90
+ ? IntersectBrand<N, PositiveNumber>
91
+ : IntersectBrand<N, NonNegativeNumber>;
91
92
 
92
93
  /**
93
94
  * Removes the non-zero brand constraint from a branded number type. Used
@@ -99,7 +100,7 @@ export namespace TsDataForgeInternals {
99
100
  export type RemoveNonZeroBrandKey<N extends UnknownNumberBrand> = Brand<
100
101
  GetBrandValuePart<N>,
101
102
  RelaxedExclude<UnwrapBrandTrueKeys<N>, '!=0'> & string,
102
- UnwrapBrandFalseKeys<N> & string
103
+ RelaxedExclude<UnwrapBrandFalseKeys<N>, '<=0' | '>=0'> & string
103
104
  >;
104
105
 
105
106
  type CastToInt<N> = N extends Int ? N : never;
@@ -120,9 +121,13 @@ export namespace TsDataForgeInternals {
120
121
  * 'positive' | 'range'
121
122
  * @internal
122
123
  */
124
+ /** The closed binary arithmetic operations a branded namespace may expose. */
125
+ export type NumberClassBinOp = 'add' | 'div' | 'mul' | 'sub';
126
+
123
127
  export type NumberClass<
124
128
  N extends UnknownNumberBrand,
125
129
  classes extends 'int' | 'non-negative' | 'positive' | 'range',
130
+ binOps extends NumberClassBinOp = NumberClassBinOp,
126
131
  > = ('int' extends classes
127
132
  ? unknown
128
133
  : 'positive' extends classes
@@ -162,16 +167,21 @@ export namespace TsDataForgeInternals {
162
167
  clamp: (a: number) => N;
163
168
  }>
164
169
  : unknown) &
170
+ Pick<
171
+ Readonly<{
172
+ add: (x: N, y: N) => N;
173
+ sub: (x: N, y: N) => N;
174
+ mul: (x: N, y: N) => N;
175
+ div: (x: N, y: ToNonZero<N>) => N;
176
+ }>,
177
+ binOps
178
+ > &
165
179
  Readonly<{
166
180
  is: (a: number) => a is N;
167
181
  min: (...values: readonly N[]) => N;
168
182
  max: (...values: readonly N[]) => N;
169
183
  random: (min: N, max: N) => N;
170
184
  pow: (x: N, y: N) => N;
171
- add: (x: N, y: N) => N;
172
- sub: (x: N, y: N) => N;
173
- mul: (x: N, y: N) => N;
174
- div: (x: N, y: ToNonZero<N>) => N;
175
185
  }>;
176
186
 
177
187
  type BaseKeys =
@@ -505,10 +505,10 @@ const deepOmitImpl = (
505
505
 
506
506
  const head = path[0];
507
507
 
508
- const tail = path.slice(1);
509
-
510
508
  if (!hasKey(record, head)) return record;
511
509
 
510
+ const tail = path.slice(1);
511
+
512
512
  if (!Arr.isNonEmpty(tail)) {
513
513
  return Object.fromEntries(
514
514
  Object.entries(record).filter(([k]) => k !== head),
@@ -1,5 +1,3 @@
1
- /* eslint-disable ts-data-forge/prefer-arr-is-array */
2
-
3
1
  import { isMap, isRegExp, isSet, isTypedArray } from '@sindresorhus/is';
4
2
  import { hasKey, isRecord } from '../guard/index.mjs';
5
3
 
@@ -1,6 +1,7 @@
1
1
  import { type DeepReadonly } from 'ts-type-forge';
2
2
  import { expectType } from '../expect-type.mjs';
3
- import { pipe } from '../functional/index.mjs';
3
+ import { pipe, Result } from '../functional/index.mjs';
4
+ import { Num } from '../number/index.mjs';
4
5
  import { mapNullable } from './map-nullable.mjs';
5
6
 
6
7
  describe(mapNullable, () => {
@@ -306,7 +307,7 @@ describe(mapNullable, () => {
306
307
  const extractAge = mapNullable((data: FormDataType) => data.age);
307
308
 
308
309
  const parseAge = mapNullable((ageStr: string) =>
309
- Number.parseInt(ageStr, 10),
310
+ Result.unwrapOkOr(Num.safeParseInt(ageStr), Number.NaN),
310
311
  );
311
312
 
312
313
  const validateAge = mapNullable((age: number) =>
@@ -336,7 +337,7 @@ describe(mapNullable, () => {
336
337
  const extractAge = mapNullable((data: FormDataType) => data.age);
337
338
 
338
339
  const parseAge = mapNullable((ageStr: string) =>
339
- Number.parseInt(ageStr, 10),
340
+ Result.unwrapOkOr(Num.safeParseInt(ageStr), Number.NaN),
340
341
  );
341
342
 
342
343
  const extractedAge = extractAge(incompleteFormData);
@@ -78,7 +78,7 @@ export const unknownToString = (
78
78
 
79
79
  // Special handling for Map
80
80
  if (isMap(value)) {
81
- const entries = Array.from(value.entries());
81
+ const entries = Array.from(value);
82
82
 
83
83
  const entriesStr = prettyPrintObject
84
84
  ? JSON.stringify(entries, undefined, 2)
@@ -89,7 +89,7 @@ export const unknownToString = (
89
89
 
90
90
  // Special handling for Set
91
91
  if (isSet(value)) {
92
- const values = Array.from(value.values());
92
+ const values = Array.from(value);
93
93
 
94
94
  const valuesStr = prettyPrintObject
95
95
  ? JSON.stringify(values, undefined, 2)