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,566 @@
1
+ import { expectType } from '../expect-type.mjs';
2
+ import { pipe } from '../functional/index.mjs';
3
+ import { Num } from './num.mjs';
4
+
5
+ /** @internal */
6
+ export namespace TsVerifiedInternals {
7
+ /**
8
+ * Internal utilities for creating and managing refined (branded) number types.
9
+ *
10
+ * This namespace provides factory functions and type utilities for building
11
+ * type-safe numeric operations with compile-time constraints. It serves as
12
+ * the foundation for all branded number types in the library, including:
13
+ * - Integer types (Int, SafeInt, Int8, Int16, Int32)
14
+ * - Unsigned types (UInt, UInt8, UInt16, UInt32)
15
+ * - Constrained types (NonZero, NonNegative, Positive)
16
+ * - Range-bounded types
17
+ *
18
+ * The utilities handle:
19
+ * - Type validation and narrowing
20
+ * - Arithmetic operations that preserve type constraints
21
+ * - Automatic clamping for bounded types
22
+ * - Random number generation within type bounds
23
+ *
24
+ * @internal This namespace is not part of the public API
25
+ */
26
+ export namespace RefinedNumberUtils {
27
+ const castTypeImpl =
28
+ <BrandedType extends number>(
29
+ is: (n: number) => n is BrandedType,
30
+ typeNameInErrorMessage: string,
31
+ ) =>
32
+ <N extends number>(a: N): BrandedType & N => {
33
+ if (!is(a)) {
34
+ throw new TypeError(`Expected ${typeNameInErrorMessage}, got: ${a}`);
35
+ }
36
+ return a;
37
+ };
38
+
39
+ type UnknownNumberBrand = ChangeBaseBrand<UnknownBrand, number>;
40
+
41
+ /**
42
+ * Converts a branded number type to include the Int brand.
43
+ * @template N - A branded number type
44
+ * @internal
45
+ */
46
+ export type ToInt<N extends UnknownNumberBrand> = IntersectBrand<N, Int>;
47
+
48
+ type ToNonZero<N extends UnknownNumberBrand> = IntersectBrand<
49
+ N,
50
+ NonZeroNumber
51
+ >;
52
+
53
+ const isNonZero = <N extends UnknownNumberBrand>(
54
+ n: N,
55
+ ): n is N & ToNonZero<N> => n !== 0;
56
+
57
+ type ToNonZeroIntWithSmallInt<N extends Int> = WithSmallInt<
58
+ CastToInt<ToNonZero<N>>
59
+ >;
60
+
61
+ /**
62
+ * Converts a branded number type to include the NonNegativeNumber brand.
63
+ * @template N - A branded number type
64
+ * @internal
65
+ */
66
+ export type ToNonNegative<N extends UnknownNumberBrand> = IntersectBrand<
67
+ N,
68
+ NonNegativeNumber
69
+ >;
70
+
71
+ /**
72
+ * Removes the non-zero brand constraint from a branded number type.
73
+ * Used when operations may produce zero values.
74
+ * @template N - A branded number type
75
+ * @internal
76
+ */
77
+ export type RemoveNonZeroBrandKey<N extends UnknownNumberBrand> = Brand<
78
+ GetBrandValuePart<N>,
79
+ RelaxedExclude<UnwrapBrandTrueKeys<N>, '!=0'> & string,
80
+ UnwrapBrandFalseKeys<N> & string
81
+ >;
82
+
83
+ type CastToInt<N> = N extends Int ? N : never;
84
+
85
+ /**
86
+ * Generates a type-safe API for a branded number type based on its characteristics.
87
+ *
88
+ * This type dynamically constructs an object type with appropriate methods based
89
+ * on the number class. For example:
90
+ * - Integer types don't get floor/ceil/round methods
91
+ * - Non-negative types don't get abs method
92
+ * - Range-bounded types get MIN_VALUE/MAX_VALUE constants
93
+ *
94
+ * @template N - The branded number type
95
+ * @template classes - Union of characteristics: 'int' | 'non-negative' | 'positive' | 'range'
96
+ * @internal
97
+ */
98
+ export type NumberClass<
99
+ N extends UnknownNumberBrand,
100
+ classes extends 'int' | 'non-negative' | 'positive' | 'range',
101
+ > = ('int' extends classes
102
+ ? unknown
103
+ : 'positive' extends classes
104
+ ? Readonly<{
105
+ floor: (x: N, y: N) => RemoveNonZeroBrandKey<ToInt<N>>;
106
+ ceil: (x: N, y: N) => ToInt<N>;
107
+ round: (x: N, y: N) => RemoveNonZeroBrandKey<ToInt<N>>;
108
+ }>
109
+ : Readonly<{
110
+ floor: (x: N, y: N) => ToInt<N>;
111
+ ceil: (x: N, y: N) => ToInt<N>;
112
+ round: (x: N, y: N) => ToInt<N>;
113
+ }>) &
114
+ ('non-negative' extends classes
115
+ ? Readonly<{
116
+ MIN_VALUE: number;
117
+ clamp: (a: number) => N;
118
+ }>
119
+ : unknown) &
120
+ ('non-negative' extends classes
121
+ ? unknown
122
+ : 'positive' extends classes
123
+ ? unknown
124
+ : Readonly<{
125
+ abs: (x: N) => ToNonNegative<N>;
126
+ }>) &
127
+ ('positive' extends classes
128
+ ? Readonly<{
129
+ MIN_VALUE: number;
130
+ clamp: (a: number) => N;
131
+ }>
132
+ : unknown) &
133
+ ('range' extends classes
134
+ ? Readonly<{
135
+ MIN_VALUE: number;
136
+ MAX_VALUE: number;
137
+ clamp: (a: number) => N;
138
+ }>
139
+ : unknown) &
140
+ Readonly<{
141
+ is: (a: number) => a is N;
142
+ min: (...values: readonly N[]) => N;
143
+ max: (...values: readonly N[]) => N;
144
+ random: (min: N, max: N) => N;
145
+ pow: (x: N, y: N) => N;
146
+ add: (x: N, y: N) => N;
147
+ sub: (x: N, y: N) => N;
148
+ mul: (x: N, y: N) => N;
149
+ div: (x: N, y: ToNonZero<N>) => N;
150
+ }>;
151
+
152
+ type BaseKeys =
153
+ | 'add'
154
+ | 'div'
155
+ | 'is'
156
+ | 'max'
157
+ | 'min'
158
+ | 'mul'
159
+ | 'pow'
160
+ | 'random'
161
+ | 'sub';
162
+
163
+ type FloatMethods = 'ceil' | 'floor' | 'round';
164
+
165
+ expectType<keyof NumberClass<UnknownNumberBrand, 'int'>, BaseKeys | 'abs'>(
166
+ '=',
167
+ );
168
+
169
+ expectType<
170
+ keyof NumberClass<UnknownNumberBrand, never>,
171
+ BaseKeys | FloatMethods | 'abs'
172
+ >('=');
173
+
174
+ expectType<
175
+ keyof NumberClass<UnknownNumberBrand, 'non-negative'>,
176
+ BaseKeys | FloatMethods | 'clamp' | 'MIN_VALUE'
177
+ >('=');
178
+
179
+ expectType<
180
+ keyof NumberClass<UnknownNumberBrand, 'positive'>,
181
+ BaseKeys | FloatMethods | 'clamp' | 'MIN_VALUE'
182
+ >('=');
183
+
184
+ expectType<
185
+ keyof NumberClass<UnknownNumberBrand, 'int' | 'range'>,
186
+ BaseKeys | 'abs' | 'clamp' | 'MAX_VALUE' | 'MIN_VALUE'
187
+ >('=');
188
+
189
+ const isFnOrUndefined = (
190
+ min: number | undefined,
191
+ max: number | undefined,
192
+ ): ((n: number) => boolean) | undefined =>
193
+ min === undefined
194
+ ? max === undefined
195
+ ? undefined
196
+ : (n) => n <= max
197
+ : max === undefined
198
+ ? (n) => min <= n
199
+ : Num.isInRangeInclusive(min, max);
200
+
201
+ const clampFnOrUndefined = (
202
+ min: number | undefined,
203
+ max: number | undefined,
204
+ ): ((n: number) => number) | undefined =>
205
+ min === undefined
206
+ ? max === undefined
207
+ ? undefined
208
+ : (n) => Math.min(max, n)
209
+ : max === undefined
210
+ ? (n) => Math.max(min, n)
211
+ : Num.clamp(min, max);
212
+
213
+ type OperatorsForInteger<
214
+ ElementType extends Int,
215
+ MIN_VALUE extends number | undefined,
216
+ MAX_VALUE extends number | undefined,
217
+ ElementTypeWithSmallInt extends
218
+ WithSmallInt<ElementType> = WithSmallInt<ElementType>,
219
+ > = Readonly<{
220
+ MIN_VALUE: MIN_VALUE;
221
+ MAX_VALUE: MAX_VALUE;
222
+
223
+ is: (a: number) => a is ElementType;
224
+
225
+ abs: (x: ElementTypeWithSmallInt) => ToNonNegative<ElementType>;
226
+
227
+ min: (...values: readonly ElementTypeWithSmallInt[]) => ElementType;
228
+
229
+ max: (...values: readonly ElementTypeWithSmallInt[]) => ElementType;
230
+
231
+ pow: (
232
+ x: ElementTypeWithSmallInt,
233
+ y: ElementTypeWithSmallInt,
234
+ ) => ElementType;
235
+
236
+ add: (
237
+ x: ElementTypeWithSmallInt,
238
+ y: ElementTypeWithSmallInt,
239
+ ) => ElementType;
240
+
241
+ sub: (
242
+ x: ElementTypeWithSmallInt,
243
+ y: ElementTypeWithSmallInt,
244
+ ) => ElementType;
245
+
246
+ mul: (
247
+ x: ElementTypeWithSmallInt,
248
+ y: ElementTypeWithSmallInt,
249
+ ) => ElementType;
250
+
251
+ div: (
252
+ x: ElementTypeWithSmallInt,
253
+ y: ToNonZeroIntWithSmallInt<ElementType>,
254
+ ) => ElementType;
255
+
256
+ random: (
257
+ min: ElementTypeWithSmallInt,
258
+ max: ElementTypeWithSmallInt,
259
+ ) => ElementType;
260
+
261
+ randomNonZero: (
262
+ min: ElementTypeWithSmallInt,
263
+ max: ElementTypeWithSmallInt,
264
+ ) => ElementType;
265
+
266
+ castType: <N extends number>(x: N) => ElementType & N;
267
+
268
+ clamp: TypeEq<MAX_VALUE | MIN_VALUE, undefined> extends true
269
+ ? undefined
270
+ : (x: number) => ElementType;
271
+ }>;
272
+
273
+ /**
274
+ * Factory function that creates a complete set of type-safe operations for integer types.
275
+ *
276
+ * This function generates:
277
+ * - Type guards and validators
278
+ * - Arithmetic operations that preserve type constraints
279
+ * - Utility functions (min, max, abs, random)
280
+ * - Automatic clamping for bounded types
281
+ *
282
+ * All operations ensure results remain within the type's constraints,
283
+ * using clamping when bounds are specified.
284
+ *
285
+ * @template ElementType - The integer branded type
286
+ * @template MIN_VALUE - Optional minimum value for bounded types
287
+ * @template MAX_VALUE - Optional maximum value for bounded types
288
+ *
289
+ * @param config - Configuration object
290
+ * @param config.integerOrSafeInteger - Whether to use Number.isInteger or Number.isSafeInteger
291
+ * @param config.nonZero - If true, excludes zero from valid values
292
+ * @param config.MIN_VALUE - Minimum valid value (inclusive)
293
+ * @param config.MAX_VALUE - Maximum valid value (inclusive)
294
+ * @param config.typeNameInMessage - Human-readable type name for error messages
295
+ *
296
+ * @returns Object containing all type-safe operations for the integer type
297
+ * @internal
298
+ */
299
+ export const operatorsForInteger = <
300
+ ElementType extends Int,
301
+ MIN_VALUE extends number | undefined,
302
+ MAX_VALUE extends number | undefined,
303
+ >({
304
+ integerOrSafeInteger,
305
+ nonZero,
306
+ MIN_VALUE,
307
+ MAX_VALUE,
308
+ typeNameInMessage,
309
+ }: Readonly<{
310
+ integerOrSafeInteger: 'Integer' | 'SafeInteger';
311
+ nonZero?: boolean;
312
+ MIN_VALUE: MIN_VALUE;
313
+ MAX_VALUE: MAX_VALUE;
314
+ typeNameInMessage: string;
315
+ }>): OperatorsForInteger<ElementType, MIN_VALUE, MAX_VALUE> => {
316
+ type ElementTypeWithSmallInt = WithSmallInt<ElementType>;
317
+
318
+ const is = (a: number): a is ElementType =>
319
+ (integerOrSafeInteger === 'Integer'
320
+ ? Number.isInteger(a)
321
+ : Number.isSafeInteger(a)) &&
322
+ (nonZero === true ? a !== 0 : true) &&
323
+ (isFnOrUndefined(MIN_VALUE, MAX_VALUE)?.(a) ?? true);
324
+
325
+ const castType = castTypeImpl<ElementType>(is, typeNameInMessage);
326
+
327
+ const clamp: ((a: number) => ElementType) | undefined = pipe(
328
+ clampFnOrUndefined(MIN_VALUE, MAX_VALUE),
329
+ ).mapNullable(
330
+ (cl) =>
331
+ (x: number): ElementType =>
332
+ castType(Math.round(cl(x))),
333
+ ).value;
334
+
335
+ const clampOrCastFn: (a: number) => ElementType = clamp ?? castType;
336
+
337
+ const abs = (x: ElementTypeWithSmallInt): ToNonNegative<ElementType> =>
338
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
339
+ Math.abs(clampOrCastFn(x)) as ToNonNegative<ElementType>;
340
+
341
+ const min_ = (
342
+ ...values: readonly ElementTypeWithSmallInt[]
343
+ ): ElementType => clampOrCastFn(Math.min(...values));
344
+
345
+ const max_ = (
346
+ ...values: readonly ElementTypeWithSmallInt[]
347
+ ): ElementType => clampOrCastFn(Math.max(...values));
348
+
349
+ const pow = (
350
+ x: ElementTypeWithSmallInt,
351
+ y: ElementTypeWithSmallInt,
352
+ ): ElementType => clampOrCastFn(x ** y);
353
+
354
+ const add = (
355
+ x: ElementTypeWithSmallInt,
356
+ y: ElementTypeWithSmallInt,
357
+ ): ElementType => clampOrCastFn(x + y);
358
+
359
+ const sub = (
360
+ x: ElementTypeWithSmallInt,
361
+ y: ElementTypeWithSmallInt,
362
+ ): ElementType => clampOrCastFn(x - y);
363
+
364
+ const mul = (
365
+ x: ElementTypeWithSmallInt,
366
+ y: ElementTypeWithSmallInt,
367
+ ): ElementType => clampOrCastFn(x * y);
368
+
369
+ const div = (
370
+ x: ElementTypeWithSmallInt,
371
+ y: ToNonZeroIntWithSmallInt<ElementType>,
372
+ ): ElementType => clampOrCastFn(Math.floor(x / y));
373
+
374
+ const randomImpl = (
375
+ min: ElementTypeWithSmallInt,
376
+ max: ElementTypeWithSmallInt,
377
+ ): number =>
378
+ min + Math.floor((Math.max(max, min) - min + 1) * Math.random());
379
+
380
+ // [-5, 5] -> floor(11 * Math.random()) + (-5)
381
+ const random = (
382
+ min: ElementTypeWithSmallInt,
383
+ max: ElementTypeWithSmallInt,
384
+ ): ElementType => clampOrCastFn(randomImpl(min, max));
385
+
386
+ const randomNonZero = (
387
+ min: ElementTypeWithSmallInt,
388
+ max: ElementTypeWithSmallInt,
389
+ ): ElementType => {
390
+ while (true) {
391
+ const r = randomImpl(min, max);
392
+ if (Num.isNonZero(r)) return clampOrCastFn(r);
393
+ }
394
+ };
395
+
396
+ return {
397
+ MIN_VALUE,
398
+ MAX_VALUE,
399
+ is,
400
+ abs,
401
+ min: min_,
402
+ max: max_,
403
+ pow,
404
+ add,
405
+ sub,
406
+ mul,
407
+ div,
408
+ random,
409
+ randomNonZero,
410
+ castType,
411
+
412
+ clamp:
413
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
414
+ clamp as TypeEq<MAX_VALUE | MIN_VALUE, undefined> extends true
415
+ ? undefined
416
+ : (x: number) => ElementType,
417
+ } as const;
418
+ };
419
+
420
+ type OperatorsForFloat<
421
+ ElementType extends UnknownNumberBrand,
422
+ MIN_VALUE extends number | undefined,
423
+ MAX_VALUE extends number | undefined,
424
+ > = Readonly<{
425
+ MIN_VALUE: MIN_VALUE;
426
+ MAX_VALUE: MAX_VALUE;
427
+
428
+ is: (a: number) => a is ElementType;
429
+
430
+ abs: (x: ElementType) => ToNonNegative<ElementType>;
431
+ min: (...values: readonly ElementType[]) => ElementType;
432
+ max: (...values: readonly ElementType[]) => ElementType;
433
+ pow: (x: ElementType, y: ElementType) => ElementType;
434
+ add: (x: ElementType, y: ElementType) => ElementType;
435
+ sub: (x: ElementType, y: ElementType) => ElementType;
436
+ mul: (x: ElementType, y: ElementType) => ElementType;
437
+ div: (x: ElementType, y: ToNonZero<ElementType>) => ElementType;
438
+ random: (min: ElementType, max: ElementType) => ElementType;
439
+ randomNonZero: (min: ElementType, max: ElementType) => ElementType;
440
+
441
+ castType: <N extends number>(x: N) => ElementType & N;
442
+
443
+ clamp: TypeEq<MAX_VALUE | MIN_VALUE, undefined> extends true
444
+ ? undefined
445
+ : (x: number) => ElementType;
446
+ }>;
447
+
448
+ /**
449
+ * Factory function that creates a complete set of type-safe operations for floating-point types.
450
+ *
451
+ * This function generates:
452
+ * - Type guards and validators (checking for finite values)
453
+ * - Arithmetic operations that preserve type constraints
454
+ * - Utility functions (min, max, abs, random)
455
+ * - Automatic clamping for bounded types
456
+ *
457
+ * All operations ensure results remain finite and within any specified bounds.
458
+ * Division by zero is prevented through type constraints.
459
+ *
460
+ * @template ElementType - The floating-point branded type
461
+ * @template MIN_VALUE - Optional minimum value for bounded types
462
+ * @template MAX_VALUE - Optional maximum value for bounded types
463
+ *
464
+ * @param config - Configuration object
465
+ * @param config.nonZero - If true, excludes zero from valid values
466
+ * @param config.MIN_VALUE - Minimum valid value (inclusive)
467
+ * @param config.MAX_VALUE - Maximum valid value (inclusive)
468
+ * @param config.typeNameInMessage - Human-readable type name for error messages
469
+ *
470
+ * @returns Object containing all type-safe operations for the floating-point type
471
+ * @internal
472
+ */
473
+ export const operatorsForFloat = <
474
+ ElementType extends UnknownNumberBrand,
475
+ MIN_VALUE extends number | undefined,
476
+ MAX_VALUE extends number | undefined,
477
+ >({
478
+ nonZero,
479
+ MIN_VALUE,
480
+ MAX_VALUE,
481
+ typeNameInMessage,
482
+ }: Readonly<{
483
+ nonZero?: boolean;
484
+ MIN_VALUE: MIN_VALUE;
485
+ MAX_VALUE: MAX_VALUE;
486
+ typeNameInMessage: string;
487
+ }>): OperatorsForFloat<ElementType, MIN_VALUE, MAX_VALUE> => {
488
+ const is = (a: number): a is ElementType =>
489
+ Number.isFinite(a) &&
490
+ (nonZero === true ? a !== 0 : true) &&
491
+ (isFnOrUndefined(MIN_VALUE, MAX_VALUE)?.(a) ?? true);
492
+
493
+ const castType = castTypeImpl<ElementType>(is, typeNameInMessage);
494
+
495
+ const clamp: ((a: number) => ElementType) | undefined = pipe(
496
+ clampFnOrUndefined(MIN_VALUE, MAX_VALUE),
497
+ ).mapNullable(
498
+ (cl) =>
499
+ (x: number): ElementType =>
500
+ castType(cl(x)),
501
+ ).value;
502
+
503
+ const clampOrCastFn: (a: number) => ElementType = clamp ?? castType;
504
+
505
+ const abs = (x: ElementType): ToNonNegative<ElementType> =>
506
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
507
+ Math.abs(x) as ToNonNegative<ElementType>;
508
+
509
+ const min_ = (...values: readonly ElementType[]): ElementType =>
510
+ clampOrCastFn(Math.min(...values));
511
+
512
+ const max_ = (...values: readonly ElementType[]): ElementType =>
513
+ clampOrCastFn(Math.max(...values));
514
+
515
+ const pow = (x: ElementType, y: ElementType): ElementType =>
516
+ clampOrCastFn(x ** y);
517
+
518
+ const add = (x: ElementType, y: ElementType): ElementType =>
519
+ clampOrCastFn(x + y);
520
+
521
+ const sub = (x: ElementType, y: ElementType): ElementType =>
522
+ clampOrCastFn(x - y);
523
+
524
+ const mul = (x: ElementType, y: ElementType): ElementType =>
525
+ clampOrCastFn(x * y);
526
+
527
+ const div = (x: ElementType, y: ToNonZero<ElementType>): ElementType =>
528
+ clampOrCastFn(x / y);
529
+
530
+ const random = (min: ElementType, max: ElementType): ElementType =>
531
+ clampOrCastFn(min + (Math.max(max, min) - min) * Math.random());
532
+
533
+ const randomNonZero = (
534
+ min: ElementType,
535
+ max: ElementType,
536
+ ): ElementType => {
537
+ while (true) {
538
+ const r = random(min, max);
539
+ if (isNonZero(r)) return r;
540
+ }
541
+ };
542
+
543
+ return {
544
+ MIN_VALUE,
545
+ MAX_VALUE,
546
+ is,
547
+ abs,
548
+ min: min_,
549
+ max: max_,
550
+ pow,
551
+ add,
552
+ sub,
553
+ mul,
554
+ div,
555
+ random,
556
+ randomNonZero,
557
+ castType,
558
+
559
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion
560
+ clamp: clamp as TypeEq<MAX_VALUE | MIN_VALUE, undefined> extends true
561
+ ? undefined
562
+ : (x: number) => ElementType,
563
+ } as const;
564
+ };
565
+ }
566
+ }
@@ -0,0 +1 @@
1
+ export * from './object.mjs';