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.
- package/LICENSE +201 -0
- package/README.md +534 -0
- package/package.json +101 -0
- package/src/array/array-utils-creation.test.mts +443 -0
- package/src/array/array-utils-modification.test.mts +197 -0
- package/src/array/array-utils-overload-type-error.test.mts +149 -0
- package/src/array/array-utils-reducing-value.test.mts +425 -0
- package/src/array/array-utils-search.test.mts +169 -0
- package/src/array/array-utils-set-op.test.mts +335 -0
- package/src/array/array-utils-slice-clamped.test.mts +113 -0
- package/src/array/array-utils-slicing.test.mts +316 -0
- package/src/array/array-utils-transformation.test.mts +790 -0
- package/src/array/array-utils-validation.test.mts +492 -0
- package/src/array/array-utils.mts +4000 -0
- package/src/array/array.test.mts +146 -0
- package/src/array/index.mts +2 -0
- package/src/array/tuple-utils.mts +519 -0
- package/src/array/tuple-utils.test.mts +518 -0
- package/src/collections/imap-mapped.mts +801 -0
- package/src/collections/imap-mapped.test.mts +860 -0
- package/src/collections/imap.mts +651 -0
- package/src/collections/imap.test.mts +932 -0
- package/src/collections/index.mts +6 -0
- package/src/collections/iset-mapped.mts +889 -0
- package/src/collections/iset-mapped.test.mts +1187 -0
- package/src/collections/iset.mts +682 -0
- package/src/collections/iset.test.mts +1084 -0
- package/src/collections/queue.mts +390 -0
- package/src/collections/queue.test.mts +282 -0
- package/src/collections/stack.mts +423 -0
- package/src/collections/stack.test.mts +225 -0
- package/src/expect-type.mts +206 -0
- package/src/functional/index.mts +4 -0
- package/src/functional/match.mts +300 -0
- package/src/functional/match.test.mts +177 -0
- package/src/functional/optional.mts +733 -0
- package/src/functional/optional.test.mts +619 -0
- package/src/functional/pipe.mts +212 -0
- package/src/functional/pipe.test.mts +85 -0
- package/src/functional/result.mts +1134 -0
- package/src/functional/result.test.mts +777 -0
- package/src/globals.d.mts +38 -0
- package/src/guard/has-key.mts +119 -0
- package/src/guard/has-key.test.mts +219 -0
- package/src/guard/index.mts +7 -0
- package/src/guard/is-non-empty-string.mts +108 -0
- package/src/guard/is-non-empty-string.test.mts +91 -0
- package/src/guard/is-non-null-object.mts +106 -0
- package/src/guard/is-non-null-object.test.mts +90 -0
- package/src/guard/is-primitive.mts +165 -0
- package/src/guard/is-primitive.test.mts +102 -0
- package/src/guard/is-record.mts +153 -0
- package/src/guard/is-record.test.mts +112 -0
- package/src/guard/is-type.mts +450 -0
- package/src/guard/is-type.test.mts +496 -0
- package/src/guard/key-is-in.mts +163 -0
- package/src/guard/key-is-in.test.mts +19 -0
- package/src/index.mts +10 -0
- package/src/iterator/index.mts +1 -0
- package/src/iterator/range.mts +120 -0
- package/src/iterator/range.test.mts +33 -0
- package/src/json/index.mts +1 -0
- package/src/json/json.mts +711 -0
- package/src/json/json.test.mts +628 -0
- package/src/number/branded-types/finite-number.mts +354 -0
- package/src/number/branded-types/finite-number.test.mts +135 -0
- package/src/number/branded-types/index.mts +26 -0
- package/src/number/branded-types/int.mts +278 -0
- package/src/number/branded-types/int.test.mts +140 -0
- package/src/number/branded-types/int16.mts +192 -0
- package/src/number/branded-types/int16.test.mts +170 -0
- package/src/number/branded-types/int32.mts +193 -0
- package/src/number/branded-types/int32.test.mts +170 -0
- package/src/number/branded-types/non-negative-finite-number.mts +223 -0
- package/src/number/branded-types/non-negative-finite-number.test.mts +188 -0
- package/src/number/branded-types/non-negative-int16.mts +187 -0
- package/src/number/branded-types/non-negative-int16.test.mts +201 -0
- package/src/number/branded-types/non-negative-int32.mts +187 -0
- package/src/number/branded-types/non-negative-int32.test.mts +204 -0
- package/src/number/branded-types/non-zero-finite-number.mts +229 -0
- package/src/number/branded-types/non-zero-finite-number.test.mts +198 -0
- package/src/number/branded-types/non-zero-int.mts +167 -0
- package/src/number/branded-types/non-zero-int.test.mts +177 -0
- package/src/number/branded-types/non-zero-int16.mts +196 -0
- package/src/number/branded-types/non-zero-int16.test.mts +195 -0
- package/src/number/branded-types/non-zero-int32.mts +196 -0
- package/src/number/branded-types/non-zero-int32.test.mts +197 -0
- package/src/number/branded-types/non-zero-safe-int.mts +196 -0
- package/src/number/branded-types/non-zero-safe-int.test.mts +232 -0
- package/src/number/branded-types/non-zero-uint16.mts +189 -0
- package/src/number/branded-types/non-zero-uint16.test.mts +199 -0
- package/src/number/branded-types/non-zero-uint32.mts +189 -0
- package/src/number/branded-types/non-zero-uint32.test.mts +199 -0
- package/src/number/branded-types/positive-finite-number.mts +241 -0
- package/src/number/branded-types/positive-finite-number.test.mts +204 -0
- package/src/number/branded-types/positive-int.mts +304 -0
- package/src/number/branded-types/positive-int.test.mts +176 -0
- package/src/number/branded-types/positive-int16.mts +188 -0
- package/src/number/branded-types/positive-int16.test.mts +197 -0
- package/src/number/branded-types/positive-int32.mts +188 -0
- package/src/number/branded-types/positive-int32.test.mts +197 -0
- package/src/number/branded-types/positive-safe-int.mts +187 -0
- package/src/number/branded-types/positive-safe-int.test.mts +210 -0
- package/src/number/branded-types/positive-uint16.mts +188 -0
- package/src/number/branded-types/positive-uint16.test.mts +203 -0
- package/src/number/branded-types/positive-uint32.mts +188 -0
- package/src/number/branded-types/positive-uint32.test.mts +203 -0
- package/src/number/branded-types/safe-int.mts +291 -0
- package/src/number/branded-types/safe-int.test.mts +170 -0
- package/src/number/branded-types/safe-uint.mts +187 -0
- package/src/number/branded-types/safe-uint.test.mts +176 -0
- package/src/number/branded-types/uint.mts +179 -0
- package/src/number/branded-types/uint.test.mts +158 -0
- package/src/number/branded-types/uint16.mts +186 -0
- package/src/number/branded-types/uint16.test.mts +170 -0
- package/src/number/branded-types/uint32.mts +218 -0
- package/src/number/branded-types/uint32.test.mts +170 -0
- package/src/number/enum/index.mts +2 -0
- package/src/number/enum/int8.mts +344 -0
- package/src/number/enum/int8.test.mts +180 -0
- package/src/number/enum/uint8.mts +293 -0
- package/src/number/enum/uint8.test.mts +164 -0
- package/src/number/index.mts +4 -0
- package/src/number/num.mts +604 -0
- package/src/number/num.test.mts +242 -0
- package/src/number/refined-number-utils.mts +566 -0
- package/src/object/index.mts +1 -0
- package/src/object/object.mts +447 -0
- package/src/object/object.test.mts +124 -0
- package/src/others/cast-mutable.mts +113 -0
- package/src/others/cast-readonly.mts +192 -0
- package/src/others/cast-readonly.test.mts +89 -0
- package/src/others/if-then.mts +98 -0
- package/src/others/if-then.test.mts +75 -0
- package/src/others/index.mts +7 -0
- package/src/others/map-nullable.mts +172 -0
- package/src/others/map-nullable.test.mts +297 -0
- package/src/others/memoize-function.mts +196 -0
- package/src/others/memoize-function.test.mts +168 -0
- package/src/others/tuple.mts +160 -0
- package/src/others/tuple.test.mts +11 -0
- package/src/others/unknown-to-string.mts +215 -0
- 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';
|