ts-data-forge 6.12.0 → 6.13.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.
- package/dist/functional/pipe.d.mts +6 -18
- package/dist/functional/pipe.d.mts.map +1 -1
- package/dist/functional/pipe.mjs +35 -15
- package/dist/functional/pipe.mjs.map +1 -1
- package/dist/number/num.d.mts +65 -1
- package/dist/number/num.d.mts.map +1 -1
- package/dist/number/num.mjs +76 -0
- package/dist/number/num.mjs.map +1 -1
- package/package.json +4 -1
- package/src/functional/pipe.mts +104 -78
- package/src/functional/pipe.test.mts +57 -1
- package/src/number/num.mts +81 -0
- package/src/number/num.test.mts +62 -0
- package/src/others/map-nullable.test.mts +3 -4
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { type MergeIntersection } from 'ts-type-forge';
|
|
2
1
|
import { Optional, type UnknownOptional } from './optional/index.mjs';
|
|
3
2
|
/**
|
|
4
3
|
* Creates a new pipe object that allows for chaining operations on a value.
|
|
@@ -18,15 +17,8 @@ import { Optional, type UnknownOptional } from './optional/index.mjs';
|
|
|
18
17
|
* @param a The initial value to wrap in a pipe.
|
|
19
18
|
* @returns A pipe object with chaining methods appropriate for the value type.
|
|
20
19
|
*/
|
|
21
|
-
export declare
|
|
22
|
-
export
|
|
23
|
-
type Pipe<A> = A extends UnknownOptional ? PipeWithMapOptional<A> : PipeBase<A>;
|
|
24
|
-
/**
|
|
25
|
-
* @template A The type of the current value in the pipe.
|
|
26
|
-
* @internal
|
|
27
|
-
* Base pipe interface providing core functionality.
|
|
28
|
-
* All pipe types extend this interface.
|
|
29
|
-
*/
|
|
20
|
+
export declare const pipe: <const A>(a: A) => Pipe<A>;
|
|
21
|
+
export type Pipe<A> = PipeBase<A> & PipeMapNullable<A> & ([A] extends [UnknownOptional] ? PipeMapOptional<A> : unknown);
|
|
30
22
|
type PipeBase<A> = Readonly<{
|
|
31
23
|
/** The current value being piped. */
|
|
32
24
|
value: A;
|
|
@@ -48,6 +40,8 @@ type PipeBase<A> = Readonly<{
|
|
|
48
40
|
* @returns A new pipe containing the transformed value.
|
|
49
41
|
*/
|
|
50
42
|
map: <B>(fn: (a: A) => B) => Pipe<B>;
|
|
43
|
+
}>;
|
|
44
|
+
type PipeMapNullable<A> = Readonly<{
|
|
51
45
|
/**
|
|
52
46
|
* Maps the current value only if it's not null or undefined. If the current
|
|
53
47
|
* value is null/undefined, the transformation is skipped and undefined is
|
|
@@ -75,13 +69,7 @@ type PipeBase<A> = Readonly<{
|
|
|
75
69
|
*/
|
|
76
70
|
mapNullable: <B>(fn: (a: NonNullable<A>) => B) => Pipe<B | undefined>;
|
|
77
71
|
}>;
|
|
78
|
-
|
|
79
|
-
* @template A The Optional type currently in the pipe.
|
|
80
|
-
* @internal
|
|
81
|
-
* Pipe interface for Optional values, providing Optional-aware mapping.
|
|
82
|
-
* Extends PipeBase with mapOptional functionality for monadic operations.
|
|
83
|
-
*/
|
|
84
|
-
type PipeWithMapOptional<A extends UnknownOptional> = MergeIntersection<PipeBase<A> & Readonly<{
|
|
72
|
+
type PipeMapOptional<A extends UnknownOptional> = Readonly<{
|
|
85
73
|
/**
|
|
86
74
|
* Maps the value inside an Optional using Optional.map semantics. If the
|
|
87
75
|
* Optional is None, the transformation is skipped and None is propagated.
|
|
@@ -109,6 +97,6 @@ type PipeWithMapOptional<A extends UnknownOptional> = MergeIntersection<PipeBase
|
|
|
109
97
|
* @returns A new pipe containing an Optional with the transformed value.
|
|
110
98
|
*/
|
|
111
99
|
mapOptional: <B>(fn: (a: Optional.Unwrap<A>) => B) => Pipe<Optional<B>>;
|
|
112
|
-
}
|
|
100
|
+
}>;
|
|
113
101
|
export {};
|
|
114
102
|
//# sourceMappingURL=pipe.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipe.d.mts","sourceRoot":"","sources":["../../src/functional/pipe.mts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"pipe.d.mts","sourceRoot":"","sources":["../../src/functional/pipe.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,KAAK,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEtE;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,IAAI,GAAI,KAAK,CAAC,CAAC,EAAG,GAAG,CAAC,KAAG,IAAI,CAAC,CAAC,CAYI,CAAC;AAmBjD,MAAM,MAAM,IAAI,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,GAC/B,eAAe,CAAC,CAAC,CAAC,GAClB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,eAAe,CAAC,GAAG,eAAe,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC;AAgCjE,KAAK,QAAQ,CAAC,CAAC,IAAI,QAAQ,CAAC;IAC1B,qCAAqC;IACrC,KAAK,EAAE,CAAC,CAAC;IAET;;;;;;;;;;;;;;;;OAgBG;IACH,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC;CACtC,CAAC,CAAC;AAEH,KAAK,eAAe,CAAC,CAAC,IAAI,QAAQ,CAAC;IACjC;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;CACvE,CAAC,CAAC;AAEH,KAAK,eAAe,CAAC,CAAC,SAAS,eAAe,IAAI,QAAQ,CAAC;IACzD;;;;;;;;;;;;;;;;;;;;;;;;;OAyBG;IACH,WAAW,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;CACzE,CAAC,CAAC"}
|
package/dist/functional/pipe.mjs
CHANGED
|
@@ -1,22 +1,42 @@
|
|
|
1
1
|
import { isOptional } from './optional/impl/optional-is-optional.mjs';
|
|
2
2
|
import { map } from './optional/impl/optional-map.mjs';
|
|
3
3
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Creates a new pipe object that allows for chaining operations on a value.
|
|
6
|
+
*
|
|
7
|
+
* This function provides a fluent interface for applying transformations to
|
|
8
|
+
* values, with intelligent method selection based on the input type:
|
|
9
|
+
*
|
|
10
|
+
* - For `Optional` values: Provides `mapOptional` for safe Optional
|
|
11
|
+
* transformations
|
|
12
|
+
* - For other values: Provides `mapNullable` for null-safe transformations
|
|
13
|
+
* - All types get the basic `map` method for general transformations
|
|
14
|
+
*
|
|
15
|
+
* The pipe maintains type safety throughout the chain, automatically selecting
|
|
16
|
+
* the appropriate overload based on the current value type.
|
|
17
|
+
*
|
|
18
|
+
* @template A The type of the initial value to wrap in a pipe.
|
|
19
|
+
* @param a The initial value to wrap in a pipe.
|
|
20
|
+
* @returns A pipe object with chaining methods appropriate for the value type.
|
|
21
|
+
*/
|
|
22
|
+
const pipe = (a) =>
|
|
23
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
24
|
+
({
|
|
25
|
+
value: a,
|
|
26
|
+
map: (fn) => pipe(fn(a)),
|
|
27
|
+
mapNullable: (fn) => pipe(a == null ? undefined : fn(a)),
|
|
28
|
+
...(isOptional(a)
|
|
29
|
+
? {
|
|
9
30
|
mapOptional: (fn) => pipe(map(a, fn)),
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}
|
|
31
|
+
}
|
|
32
|
+
: {}),
|
|
33
|
+
});
|
|
34
|
+
// NOTE: 以下では 'typing when used with generics' test で型エラーが出る。
|
|
35
|
+
// 詳細は documents/reports/pipe-typing.md の 「mapNullable を条件付きで含めるのがうまくいかない理由」を参照。
|
|
36
|
+
// export type Pipe<A> = PipeBase<A> &
|
|
37
|
+
// ([A] extends [UnknownOptional] ? PipeMapOptional<A> : unknown) &
|
|
38
|
+
// ([undefined] extends [A] ? PipeMapNullable<A> : unknown) &
|
|
39
|
+
// ([null] extends [A] ? PipeMapNullable<A> : unknown);
|
|
20
40
|
|
|
21
41
|
export { pipe };
|
|
22
42
|
//# sourceMappingURL=pipe.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipe.mjs","sources":["../../src/functional/pipe.mts"],"sourcesContent":[null],"names":["Optional.isOptional","Optional.map"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"pipe.mjs","sources":["../../src/functional/pipe.mts"],"sourcesContent":[null],"names":["Optional.isOptional","Optional.map"],"mappings":";;;AAGA;;;;;;;;;;;;;;;;;AAiBG;AACI,MAAM,IAAI,GAAG,CAAW,CAAI;AACjC;AACA,CAAC;AACC,IAAA,KAAK,EAAE,CAAC;AACR,IAAA,GAAG,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxB,WAAW,EAAE,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,IAAI,IAAI,GAAG,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;AAExD,IAAA,IAAIA,UAAmB,CAAC,CAAC;AACvB,UAAE;AACE,YAAA,WAAW,EAAE,CAAC,EAAE,KAAK,IAAI,CAACC,GAAY,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AAC/C;UACD,EAAE,CAAC;AACR,CAAA;AAuBH;AACA;AACA;AACA;AACA;AACA;;;;"}
|
package/dist/number/num.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Decrement, type Increment, type Index, type Int, type Min, type NaNType, type NegativeIndex, type NonNegativeNumber, type NonZeroNumber, type PositiveNumber, type PositiveSafeIntWithSmallInt, type RelaxedExclude, type SmallInt, type SmallUint } from 'ts-type-forge';
|
|
1
|
+
import { type Decrement, type FiniteNumber, type Increment, type Index, type Int, type Min, type NaNType, type NegativeIndex, type NonNegativeNumber, type NonZeroNumber, type PositiveNumber, type PositiveSafeIntWithSmallInt, type RelaxedExclude, type SmallInt, type SmallUint } from 'ts-type-forge';
|
|
2
2
|
import { Result } from '../functional/index.mjs';
|
|
3
3
|
import { type SmallPositiveInt } from '../types.mjs';
|
|
4
4
|
/**
|
|
@@ -98,6 +98,70 @@ export declare namespace Num {
|
|
|
98
98
|
* wrapping an `Error` describing the invalid input.
|
|
99
99
|
*/
|
|
100
100
|
export const safeParseInt: (s: string) => Result<Int, Error>;
|
|
101
|
+
/**
|
|
102
|
+
* Safely parses a finite floating-point number from a string, returning a
|
|
103
|
+
* {@link Result} that is `Ok<FiniteNumber>` for valid input and `Err<Error>`
|
|
104
|
+
* otherwise.
|
|
105
|
+
*
|
|
106
|
+
* This is a stricter alternative to both `parseFloat` and `Number`:
|
|
107
|
+
*
|
|
108
|
+
* - Unlike `parseFloat('12abc')` (which returns `12`), trailing non-numeric
|
|
109
|
+
* characters make the whole input invalid and yield `Err`.
|
|
110
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
111
|
+
* whitespace-only input yields `Err`.
|
|
112
|
+
* - Unlike `Number('Infinity')` (which returns `Infinity`), non-finite values
|
|
113
|
+
* yield `Err`.
|
|
114
|
+
*
|
|
115
|
+
* The empty-string case is rejected by delegating to `parseFloat` (which
|
|
116
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
117
|
+
* garbage case is rejected via `Number`. Decimal values are preserved as-is,
|
|
118
|
+
* so `'12.9'` stays `12.9`.
|
|
119
|
+
*
|
|
120
|
+
* Use `Result.unwrapOk` (optionally with a `?? Number.NaN` fallback) or
|
|
121
|
+
* `Result.unwrapOkOr` to get a plain number back.
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
*
|
|
125
|
+
* ```ts
|
|
126
|
+
* assert.strictEqual(
|
|
127
|
+
* Result.unwrapOkOr(Num.safeParseFloat('12.9'), Number.NaN),
|
|
128
|
+
* 12.9,
|
|
129
|
+
* );
|
|
130
|
+
*
|
|
131
|
+
* assert.strictEqual(
|
|
132
|
+
* Result.unwrapOkOr(Num.safeParseFloat('-3.5'), Number.NaN),
|
|
133
|
+
* -3.5,
|
|
134
|
+
* );
|
|
135
|
+
*
|
|
136
|
+
* assert.strictEqual(
|
|
137
|
+
* Result.unwrapOkOr(Num.safeParseFloat('1e3'), Number.NaN),
|
|
138
|
+
* 1000,
|
|
139
|
+
* );
|
|
140
|
+
*
|
|
141
|
+
* // Native `parseFloat` ignores trailing non-numeric characters
|
|
142
|
+
*
|
|
143
|
+
* assert.strictEqual(Number.parseFloat('12px'), 12);
|
|
144
|
+
*
|
|
145
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
146
|
+
*
|
|
147
|
+
* // Whitespace is not a valid number, so we return an error instead of coercing to 0.
|
|
148
|
+
*
|
|
149
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
150
|
+
*
|
|
151
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
152
|
+
*
|
|
153
|
+
* // Infinity and NaN are not finite, so they are rejected.
|
|
154
|
+
*
|
|
155
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
156
|
+
*
|
|
157
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
158
|
+
* ```
|
|
159
|
+
*
|
|
160
|
+
* @param s The string to parse.
|
|
161
|
+
* @returns `Result.ok(parsedFloat)` for valid finite input, otherwise
|
|
162
|
+
* `Result.err` wrapping an `Error` describing the invalid input.
|
|
163
|
+
*/
|
|
164
|
+
export const safeParseFloat: (s: string) => Result<FiniteNumber, Error>;
|
|
101
165
|
/**
|
|
102
166
|
* Type guard that checks if a number is non-zero.
|
|
103
167
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"num.d.mts","sourceRoot":"","sources":["../../src/number/num.mts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"num.d.mts","sourceRoot":"","sources":["../../src/number/num.mts"],"names":[],"mappings":"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"}
|
package/dist/number/num.mjs
CHANGED
|
@@ -109,6 +109,82 @@ var Num;
|
|
|
109
109
|
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
110
110
|
ok(Math.trunc(viaNumber));
|
|
111
111
|
};
|
|
112
|
+
/**
|
|
113
|
+
* Safely parses a finite floating-point number from a string, returning a
|
|
114
|
+
* {@link Result} that is `Ok<FiniteNumber>` for valid input and `Err<Error>`
|
|
115
|
+
* otherwise.
|
|
116
|
+
*
|
|
117
|
+
* This is a stricter alternative to both `parseFloat` and `Number`:
|
|
118
|
+
*
|
|
119
|
+
* - Unlike `parseFloat('12abc')` (which returns `12`), trailing non-numeric
|
|
120
|
+
* characters make the whole input invalid and yield `Err`.
|
|
121
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
122
|
+
* whitespace-only input yields `Err`.
|
|
123
|
+
* - Unlike `Number('Infinity')` (which returns `Infinity`), non-finite values
|
|
124
|
+
* yield `Err`.
|
|
125
|
+
*
|
|
126
|
+
* The empty-string case is rejected by delegating to `parseFloat` (which
|
|
127
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
128
|
+
* garbage case is rejected via `Number`. Decimal values are preserved as-is,
|
|
129
|
+
* so `'12.9'` stays `12.9`.
|
|
130
|
+
*
|
|
131
|
+
* Use `Result.unwrapOk` (optionally with a `?? Number.NaN` fallback) or
|
|
132
|
+
* `Result.unwrapOkOr` to get a plain number back.
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
*
|
|
136
|
+
* ```ts
|
|
137
|
+
* assert.strictEqual(
|
|
138
|
+
* Result.unwrapOkOr(Num.safeParseFloat('12.9'), Number.NaN),
|
|
139
|
+
* 12.9,
|
|
140
|
+
* );
|
|
141
|
+
*
|
|
142
|
+
* assert.strictEqual(
|
|
143
|
+
* Result.unwrapOkOr(Num.safeParseFloat('-3.5'), Number.NaN),
|
|
144
|
+
* -3.5,
|
|
145
|
+
* );
|
|
146
|
+
*
|
|
147
|
+
* assert.strictEqual(
|
|
148
|
+
* Result.unwrapOkOr(Num.safeParseFloat('1e3'), Number.NaN),
|
|
149
|
+
* 1000,
|
|
150
|
+
* );
|
|
151
|
+
*
|
|
152
|
+
* // Native `parseFloat` ignores trailing non-numeric characters
|
|
153
|
+
*
|
|
154
|
+
* assert.strictEqual(Number.parseFloat('12px'), 12);
|
|
155
|
+
*
|
|
156
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
157
|
+
*
|
|
158
|
+
* // Whitespace is not a valid number, so we return an error instead of coercing to 0.
|
|
159
|
+
*
|
|
160
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
161
|
+
*
|
|
162
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
163
|
+
*
|
|
164
|
+
* // Infinity and NaN are not finite, so they are rejected.
|
|
165
|
+
*
|
|
166
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
167
|
+
*
|
|
168
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
169
|
+
* ```
|
|
170
|
+
*
|
|
171
|
+
* @param s The string to parse.
|
|
172
|
+
* @returns `Result.ok(parsedFloat)` for valid finite input, otherwise
|
|
173
|
+
* `Result.err` wrapping an `Error` describing the invalid input.
|
|
174
|
+
*/
|
|
175
|
+
Num.safeParseFloat = (s) => {
|
|
176
|
+
const viaNumber = Number(s);
|
|
177
|
+
// `Number('')` / `Number(' ')` は 0 を返すが、`parseFloat` は NaN を返す。
|
|
178
|
+
// 末尾不正文字 ('12abc' 等) は `Number` 側が NaN にするので、両者が共に
|
|
179
|
+
// 非 NaN かつ有限の場合のみ採用することで空文字・空白のみ・末尾不正・
|
|
180
|
+
// Infinity をまとめて弾く。
|
|
181
|
+
return Number.isNaN(viaNumber) ||
|
|
182
|
+
!Number.isFinite(viaNumber) ||
|
|
183
|
+
Number.isNaN(Number.parseFloat(s))
|
|
184
|
+
? err(new Error(`safeParseFloat: "${s}" is not a valid finite number`))
|
|
185
|
+
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
186
|
+
ok(viaNumber);
|
|
187
|
+
};
|
|
112
188
|
/**
|
|
113
189
|
* Type guard that checks if a number is non-zero.
|
|
114
190
|
*
|
package/dist/number/num.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"num.mjs","sources":["../../src/number/num.mts"],"sourcesContent":[null],"names":["Result.err","Result.ok"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"num.mjs","sources":["../../src/number/num.mts"],"sourcesContent":[null],"names":["Result.err","Result.ok"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ts-data-forge",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.13.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -42,6 +42,9 @@
|
|
|
42
42
|
"clean": "pnpm run /clean:.*/",
|
|
43
43
|
"clean:logs": "npx rimraf ./*.log",
|
|
44
44
|
"codemod": "run-s codemod:uncommitted:as-const fmt codemod:uncommitted:readonly fmt",
|
|
45
|
+
"codemod:diff": "run-s codemod:diff:as-const fmt codemod:diff:readonly fmt",
|
|
46
|
+
"codemod:diff:as-const": "append-as-const --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
|
47
|
+
"codemod:diff:readonly": "convert-to-readonly --diff-from origin/main '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
|
45
48
|
"codemod:full": "run-s codemod:full:as-const fmt codemod:full:readonly fmt",
|
|
46
49
|
"codemod:full:as-const": "append-as-const '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
|
47
50
|
"codemod:full:readonly": "convert-to-readonly '{src,scripts,samples,test}/**/*.{mts,tsx}'",
|
package/src/functional/pipe.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { expectType } from '../expect-type.mjs';
|
|
2
2
|
import { Optional, type UnknownOptional } from './optional/index.mjs';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -19,36 +19,71 @@ import { Optional, type UnknownOptional } from './optional/index.mjs';
|
|
|
19
19
|
* @param a The initial value to wrap in a pipe.
|
|
20
20
|
* @returns A pipe object with chaining methods appropriate for the value type.
|
|
21
21
|
*/
|
|
22
|
-
export
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
22
|
+
export const pipe = <const A,>(a: A): Pipe<A> =>
|
|
23
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
24
|
+
({
|
|
25
|
+
value: a,
|
|
26
|
+
map: (fn) => pipe(fn(a)),
|
|
27
|
+
mapNullable: (fn) => pipe(a == null ? undefined : fn(a)),
|
|
28
|
+
|
|
29
|
+
...(Optional.isOptional(a)
|
|
30
|
+
? {
|
|
31
|
+
mapOptional: (fn) => pipe(Optional.map(a, fn)),
|
|
32
|
+
}
|
|
33
|
+
: {}),
|
|
34
|
+
}) satisfies PipeImpl<A> as unknown as Pipe<A>;
|
|
35
|
+
|
|
36
|
+
// `PipeBase<A>` is always present; the conditional only *adds* `mapOptional`
|
|
37
|
+
// for Optional values. Two subtleties motivate this shape:
|
|
38
|
+
//
|
|
39
|
+
// 1. The check is `[A] extends [UnknownOptional]` (a 1-tuple), not the bare
|
|
40
|
+
// `A extends UnknownOptional`. A bare check distributes over unions, so a
|
|
41
|
+
// piped union value such as `Result<D, E> | undefined` would expand into
|
|
42
|
+
// `Pipe<Ok<D>> | Pipe<Err<E>> | Pipe<undefined>`. Calling a chained method
|
|
43
|
+
// on that union of pipe objects forces TypeScript to synthesize a merged
|
|
44
|
+
// call signature, during which a branded member like `Ok<D>` is widened to
|
|
45
|
+
// its constraint (`Ok<number>`) — losing the brand `D`.
|
|
46
|
+
//
|
|
47
|
+
// 2. `value: A` lives on `PipeBase<A>`, *outside* the conditional. If `value`
|
|
48
|
+
// flowed through the conditional, then for a not-yet-resolved generic `A`
|
|
49
|
+
// the deferred true branch narrows `A` to `A & UnknownOptional`, so
|
|
50
|
+
// `.value` would surface as the noisy `(A & UnknownOptional) | A`. Keeping
|
|
51
|
+
// it on the unconditional base yields a clean `A`.
|
|
52
|
+
// transformer-ignore-next-line convert-to-readonly
|
|
53
|
+
export type Pipe<A> = PipeBase<A> &
|
|
54
|
+
PipeMapNullable<A> &
|
|
55
|
+
([A] extends [UnknownOptional] ? PipeMapOptional<A> : unknown);
|
|
56
|
+
|
|
57
|
+
// NOTE: 以下では 'typing when used with generics' test で型エラーが出る。
|
|
58
|
+
// 詳細は documents/reports/pipe-typing.md の 「mapNullable を条件付きで含めるのがうまくいかない理由」を参照。
|
|
59
|
+
// export type Pipe<A> = PipeBase<A> &
|
|
60
|
+
// ([A] extends [UnknownOptional] ? PipeMapOptional<A> : unknown) &
|
|
61
|
+
// ([undefined] extends [A] ? PipeMapNullable<A> : unknown) &
|
|
62
|
+
// ([null] extends [A] ? PipeMapNullable<A> : unknown);
|
|
63
|
+
|
|
64
|
+
expectType<Optional<number>, UnknownOptional>('<=');
|
|
65
|
+
|
|
66
|
+
expectType<number | undefined, undefined>('>=');
|
|
67
|
+
|
|
68
|
+
expectType<keyof Pipe<number | undefined>, 'value' | 'map' | 'mapNullable'>(
|
|
69
|
+
'=',
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
expectType<
|
|
73
|
+
keyof Pipe<Optional<number>>,
|
|
74
|
+
'value' | 'map' | 'mapNullable' | 'mapOptional'
|
|
75
|
+
>('=');
|
|
76
|
+
|
|
77
|
+
expectType<
|
|
78
|
+
keyof Pipe<Optional<number | undefined>>,
|
|
79
|
+
'value' | 'map' | 'mapNullable' | 'mapOptional'
|
|
80
|
+
>('=');
|
|
81
|
+
|
|
82
|
+
expectType<
|
|
83
|
+
keyof Pipe<Optional<number> | undefined>,
|
|
84
|
+
'value' | 'map' | 'mapNullable'
|
|
85
|
+
>('=');
|
|
45
86
|
|
|
46
|
-
/**
|
|
47
|
-
* @template A The type of the current value in the pipe.
|
|
48
|
-
* @internal
|
|
49
|
-
* Base pipe interface providing core functionality.
|
|
50
|
-
* All pipe types extend this interface.
|
|
51
|
-
*/
|
|
52
87
|
type PipeBase<A> = Readonly<{
|
|
53
88
|
/** The current value being piped. */
|
|
54
89
|
value: A;
|
|
@@ -71,7 +106,9 @@ type PipeBase<A> = Readonly<{
|
|
|
71
106
|
* @returns A new pipe containing the transformed value.
|
|
72
107
|
*/
|
|
73
108
|
map: <B>(fn: (a: A) => B) => Pipe<B>;
|
|
109
|
+
}>;
|
|
74
110
|
|
|
111
|
+
type PipeMapNullable<A> = Readonly<{
|
|
75
112
|
/**
|
|
76
113
|
* Maps the current value only if it's not null or undefined. If the current
|
|
77
114
|
* value is null/undefined, the transformation is skipped and undefined is
|
|
@@ -100,56 +137,45 @@ type PipeBase<A> = Readonly<{
|
|
|
100
137
|
mapNullable: <B>(fn: (a: NonNullable<A>) => B) => Pipe<B | undefined>;
|
|
101
138
|
}>;
|
|
102
139
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
* ```
|
|
133
|
-
*
|
|
134
|
-
* @template B The type of the transformed inner value.
|
|
135
|
-
* @param fn Function to transform the inner value of the Optional.
|
|
136
|
-
* @returns A new pipe containing an Optional with the transformed value.
|
|
137
|
-
*/
|
|
138
|
-
mapOptional: <B>(fn: (a: Optional.Unwrap<A>) => B) => Pipe<Optional<B>>;
|
|
139
|
-
}>
|
|
140
|
-
>;
|
|
140
|
+
type PipeMapOptional<A extends UnknownOptional> = Readonly<{
|
|
141
|
+
/**
|
|
142
|
+
* Maps the value inside an Optional using Optional.map semantics. If the
|
|
143
|
+
* Optional is None, the transformation is skipped and None is propagated.
|
|
144
|
+
* If the Optional is Some, the transformation is applied to the inner
|
|
145
|
+
* value.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
*
|
|
149
|
+
* ```ts
|
|
150
|
+
* const result = pipe(Optional.some(10))
|
|
151
|
+
* .mapOptional((value) => value * 2)
|
|
152
|
+
* .mapOptional((value) => value + 5);
|
|
153
|
+
*
|
|
154
|
+
* assert.deepStrictEqual(result.value, Optional.some(25));
|
|
155
|
+
*
|
|
156
|
+
* const empty = pipe(Optional.none as Optional<number>).mapOptional(
|
|
157
|
+
* (value) => value * 2,
|
|
158
|
+
* );
|
|
159
|
+
*
|
|
160
|
+
* assert.isTrue(Optional.isNone(empty.value));
|
|
161
|
+
* ```
|
|
162
|
+
*
|
|
163
|
+
* @template B The type of the transformed inner value.
|
|
164
|
+
* @param fn Function to transform the inner value of the Optional.
|
|
165
|
+
* @returns A new pipe containing an Optional with the transformed value.
|
|
166
|
+
*/
|
|
167
|
+
mapOptional: <B>(fn: (a: Optional.Unwrap<A>) => B) => Pipe<Optional<B>>;
|
|
168
|
+
}>;
|
|
141
169
|
|
|
142
170
|
/** @internal */
|
|
143
171
|
type Cast<T, U> = T & U;
|
|
144
172
|
|
|
145
173
|
/** @internal */
|
|
146
|
-
type PipeImpl<A> =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
}>
|
|
155
|
-
>;
|
|
174
|
+
type PipeImpl<A> = Readonly<{
|
|
175
|
+
value: A;
|
|
176
|
+
map: <B>(fn: (a: A) => B) => PipeImpl<B>;
|
|
177
|
+
mapNullable?: <B>(fn: (a: NonNullable<A>) => B) => PipeImpl<B | undefined>;
|
|
178
|
+
mapOptional?: <B>(
|
|
179
|
+
fn: (a: Optional.Unwrap<Cast<A, UnknownOptional>>) => B,
|
|
180
|
+
) => PipeImpl<Optional<B>>;
|
|
181
|
+
}>;
|
|
@@ -2,6 +2,7 @@ import { expectType } from '../expect-type.mjs';
|
|
|
2
2
|
import { type None, type Some } from '../types.mjs';
|
|
3
3
|
import { Optional } from './optional/index.mjs';
|
|
4
4
|
import { pipe } from './pipe.mjs';
|
|
5
|
+
import { Result } from './result/index.mjs';
|
|
5
6
|
|
|
6
7
|
describe(pipe, () => {
|
|
7
8
|
test('basic pipe operations', () => {
|
|
@@ -35,7 +36,9 @@ describe(pipe, () => {
|
|
|
35
36
|
});
|
|
36
37
|
|
|
37
38
|
test('mapNullable with non-null value', () => {
|
|
38
|
-
const
|
|
39
|
+
const s = 5 as number | null;
|
|
40
|
+
|
|
41
|
+
const result = pipe(s).mapNullable((x) => x * 2).value;
|
|
39
42
|
|
|
40
43
|
expect(result).toBe(10);
|
|
41
44
|
|
|
@@ -95,4 +98,57 @@ describe(pipe, () => {
|
|
|
95
98
|
|
|
96
99
|
expectType<typeof result, string>('=');
|
|
97
100
|
});
|
|
101
|
+
|
|
102
|
+
test('typing when used with Result', () => {
|
|
103
|
+
const validate = <D,>(_u: unknown): Result<D, unknown> =>
|
|
104
|
+
Result.err(undefined);
|
|
105
|
+
|
|
106
|
+
const decodeBranded = (
|
|
107
|
+
input: string,
|
|
108
|
+
parse: (s: string) => number | undefined,
|
|
109
|
+
): number | undefined => {
|
|
110
|
+
const v = pipe(input)
|
|
111
|
+
.map(parse)
|
|
112
|
+
.mapNullable(validate<number>)
|
|
113
|
+
.mapNullable((res) => Result.unwrapOk(res)).value;
|
|
114
|
+
|
|
115
|
+
expectType<typeof v, number | undefined>('=');
|
|
116
|
+
|
|
117
|
+
return v;
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
expect(decodeBranded('', () => undefined)).toBeUndefined();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test('typing when used with generics', () => {
|
|
124
|
+
const validate = <D,>(_u: unknown): Result<D, unknown> =>
|
|
125
|
+
Result.err(undefined);
|
|
126
|
+
|
|
127
|
+
// Regression: a generic brand `D` must survive the whole chain.
|
|
128
|
+
//
|
|
129
|
+
// The trigger is a `.map` whose result is a branded *union* such as
|
|
130
|
+
// `D | undefined`. If `Pipe<A>` distributes over unions, that step expands
|
|
131
|
+
// into `Pipe<D> | Pipe<undefined>`; calling the next method on the union of
|
|
132
|
+
// pipe objects then widens `D` to its constraint `number`, so `.value`
|
|
133
|
+
// collapses to `number | undefined` instead of `D | undefined`.
|
|
134
|
+
//
|
|
135
|
+
// The explicit `D | undefined` return annotation is itself the assertion:
|
|
136
|
+
// were the brand lost, `.value` would be `number | undefined` and the
|
|
137
|
+
// body would fail to compile.
|
|
138
|
+
const decodeBranded = <D extends number>(
|
|
139
|
+
input: string,
|
|
140
|
+
parse: (s: string) => D | undefined,
|
|
141
|
+
): D | undefined => {
|
|
142
|
+
const v = pipe(input)
|
|
143
|
+
.map(parse)
|
|
144
|
+
.mapNullable((n) => validate<D>(n))
|
|
145
|
+
.mapNullable((res) => Result.unwrapOk(res)).value;
|
|
146
|
+
|
|
147
|
+
expectType<typeof v, D | undefined>('=');
|
|
148
|
+
|
|
149
|
+
return v;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
expect(decodeBranded('', () => undefined)).toBeUndefined();
|
|
153
|
+
});
|
|
98
154
|
});
|
package/src/number/num.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Decrement,
|
|
3
|
+
type FiniteNumber,
|
|
3
4
|
type Increment,
|
|
4
5
|
type Index,
|
|
5
6
|
type Int,
|
|
@@ -130,6 +131,86 @@ export namespace Num {
|
|
|
130
131
|
Result.ok(Math.trunc(viaNumber) as Int);
|
|
131
132
|
};
|
|
132
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Safely parses a finite floating-point number from a string, returning a
|
|
136
|
+
* {@link Result} that is `Ok<FiniteNumber>` for valid input and `Err<Error>`
|
|
137
|
+
* otherwise.
|
|
138
|
+
*
|
|
139
|
+
* This is a stricter alternative to both `parseFloat` and `Number`:
|
|
140
|
+
*
|
|
141
|
+
* - Unlike `parseFloat('12abc')` (which returns `12`), trailing non-numeric
|
|
142
|
+
* characters make the whole input invalid and yield `Err`.
|
|
143
|
+
* - Unlike `Number('')` / `Number(' ')` (which return `0`), empty or
|
|
144
|
+
* whitespace-only input yields `Err`.
|
|
145
|
+
* - Unlike `Number('Infinity')` (which returns `Infinity`), non-finite values
|
|
146
|
+
* yield `Err`.
|
|
147
|
+
*
|
|
148
|
+
* The empty-string case is rejected by delegating to `parseFloat` (which
|
|
149
|
+
* returns `NaN` there) rather than hard-coding a check, while the trailing-
|
|
150
|
+
* garbage case is rejected via `Number`. Decimal values are preserved as-is,
|
|
151
|
+
* so `'12.9'` stays `12.9`.
|
|
152
|
+
*
|
|
153
|
+
* Use `Result.unwrapOk` (optionally with a `?? Number.NaN` fallback) or
|
|
154
|
+
* `Result.unwrapOkOr` to get a plain number back.
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
*
|
|
158
|
+
* ```ts
|
|
159
|
+
* assert.strictEqual(
|
|
160
|
+
* Result.unwrapOkOr(Num.safeParseFloat('12.9'), Number.NaN),
|
|
161
|
+
* 12.9,
|
|
162
|
+
* );
|
|
163
|
+
*
|
|
164
|
+
* assert.strictEqual(
|
|
165
|
+
* Result.unwrapOkOr(Num.safeParseFloat('-3.5'), Number.NaN),
|
|
166
|
+
* -3.5,
|
|
167
|
+
* );
|
|
168
|
+
*
|
|
169
|
+
* assert.strictEqual(
|
|
170
|
+
* Result.unwrapOkOr(Num.safeParseFloat('1e3'), Number.NaN),
|
|
171
|
+
* 1000,
|
|
172
|
+
* );
|
|
173
|
+
*
|
|
174
|
+
* // Native `parseFloat` ignores trailing non-numeric characters
|
|
175
|
+
*
|
|
176
|
+
* assert.strictEqual(Number.parseFloat('12px'), 12);
|
|
177
|
+
*
|
|
178
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
179
|
+
*
|
|
180
|
+
* // Whitespace is not a valid number, so we return an error instead of coercing to 0.
|
|
181
|
+
*
|
|
182
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
183
|
+
*
|
|
184
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
185
|
+
*
|
|
186
|
+
* // Infinity and NaN are not finite, so they are rejected.
|
|
187
|
+
*
|
|
188
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
189
|
+
*
|
|
190
|
+
* assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
191
|
+
* ```
|
|
192
|
+
*
|
|
193
|
+
* @param s The string to parse.
|
|
194
|
+
* @returns `Result.ok(parsedFloat)` for valid finite input, otherwise
|
|
195
|
+
* `Result.err` wrapping an `Error` describing the invalid input.
|
|
196
|
+
*/
|
|
197
|
+
export const safeParseFloat = (s: string): Result<FiniteNumber, Error> => {
|
|
198
|
+
const viaNumber = Number(s);
|
|
199
|
+
|
|
200
|
+
// `Number('')` / `Number(' ')` は 0 を返すが、`parseFloat` は NaN を返す。
|
|
201
|
+
// 末尾不正文字 ('12abc' 等) は `Number` 側が NaN にするので、両者が共に
|
|
202
|
+
// 非 NaN かつ有限の場合のみ採用することで空文字・空白のみ・末尾不正・
|
|
203
|
+
// Infinity をまとめて弾く。
|
|
204
|
+
return Number.isNaN(viaNumber) ||
|
|
205
|
+
!Number.isFinite(viaNumber) ||
|
|
206
|
+
Number.isNaN(Number.parseFloat(s))
|
|
207
|
+
? Result.err(
|
|
208
|
+
new Error(`safeParseFloat: "${s}" is not a valid finite number`),
|
|
209
|
+
)
|
|
210
|
+
: // eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
211
|
+
Result.ok(viaNumber as FiniteNumber);
|
|
212
|
+
};
|
|
213
|
+
|
|
133
214
|
/**
|
|
134
215
|
* Type guard that checks if a number is non-zero.
|
|
135
216
|
*
|
package/src/number/num.test.mts
CHANGED
|
@@ -190,6 +190,68 @@ describe('Num test', () => {
|
|
|
190
190
|
});
|
|
191
191
|
});
|
|
192
192
|
|
|
193
|
+
describe('safeParseFloat', () => {
|
|
194
|
+
test('parses valid numeric strings into Ok', () => {
|
|
195
|
+
expect(Result.unwrapOk(Num.safeParseFloat('123'))).toBe(123);
|
|
196
|
+
|
|
197
|
+
expect(Result.unwrapOk(Num.safeParseFloat('-42'))).toBe(-42);
|
|
198
|
+
|
|
199
|
+
expect(Result.unwrapOk(Num.safeParseFloat('+7'))).toBe(7);
|
|
200
|
+
|
|
201
|
+
expect(Result.unwrapOk(Num.safeParseFloat('0'))).toBe(0);
|
|
202
|
+
|
|
203
|
+
expect(Result.unwrapOk(Num.safeParseFloat(' 12 '))).toBe(12);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('preserves decimal values', () => {
|
|
207
|
+
expect(Result.unwrapOk(Num.safeParseFloat('12.9'))).toBe(12.9);
|
|
208
|
+
|
|
209
|
+
expect(Result.unwrapOk(Num.safeParseFloat('-3.5'))).toBe(-3.5);
|
|
210
|
+
|
|
211
|
+
expect(Result.unwrapOk(Num.safeParseFloat('1e3'))).toBe(1000);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('rejects trailing non-numeric characters (unlike parseFloat)', () => {
|
|
215
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('123abc')));
|
|
216
|
+
|
|
217
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('12px')));
|
|
218
|
+
|
|
219
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('abc')));
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('rejects empty / whitespace-only input (unlike Number)', () => {
|
|
223
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('')));
|
|
224
|
+
|
|
225
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat(' ')));
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
test('rejects non-finite values', () => {
|
|
229
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('Infinity')));
|
|
230
|
+
|
|
231
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('-Infinity')));
|
|
232
|
+
|
|
233
|
+
assert.isTrue(Result.isErr(Num.safeParseFloat('NaN')));
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
test('the Err carries a descriptive Error', () => {
|
|
237
|
+
const result = Num.safeParseFloat('nope');
|
|
238
|
+
|
|
239
|
+
assert.isTrue(Result.isErr(result));
|
|
240
|
+
|
|
241
|
+
if (Result.isErr(result)) {
|
|
242
|
+
expect(Result.unwrapErr(result)).toBeInstanceOf(Error);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
test('composes with Result.unwrapOk + nullish fallback', () => {
|
|
247
|
+
expect(Result.unwrapOk(Num.safeParseFloat('3.14')) ?? Number.NaN).toBe(
|
|
248
|
+
3.14,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
expect(Result.unwrapOk(Num.safeParseFloat('')) ?? Number.NaN).toBeNaN();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
|
|
193
255
|
describe('isInRange', () => {
|
|
194
256
|
test('checks range (lower inclusive, upper exclusive)', () => {
|
|
195
257
|
const inRange = Num.isInRange(0, 10);
|
|
@@ -143,10 +143,9 @@ describe(mapNullable, () => {
|
|
|
143
143
|
|
|
144
144
|
const toUpperCase = mapNullable((s: string) => s.toUpperCase());
|
|
145
145
|
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
.map(toUpperCase).value;
|
|
146
|
+
const x = 42 as number | null;
|
|
147
|
+
|
|
148
|
+
const result = pipe(x).map(toStr).map(addPrefix).map(toUpperCase).value;
|
|
150
149
|
|
|
151
150
|
expect(result).toBe('NUMBER: 42');
|
|
152
151
|
});
|