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,1134 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unsafe-type-assertion */
|
|
2
|
+
import { isRecord } from '../guard/index.mjs';
|
|
3
|
+
import { unknownToString } from '../others/index.mjs';
|
|
4
|
+
import { Optional } from './optional.mjs';
|
|
5
|
+
|
|
6
|
+
/** @internal Symbol to identify the 'Ok' variant of Result. */
|
|
7
|
+
const OkTypeSymbol: unique symbol = Symbol('Result.ok');
|
|
8
|
+
|
|
9
|
+
/** @internal Symbol to identify the 'Err' variant of Result. */
|
|
10
|
+
const ErrTypeSymbol: unique symbol = Symbol('Result.err');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* @internal
|
|
14
|
+
* Represents the 'Ok' variant of a Result, containing a success value.
|
|
15
|
+
* @template S The type of the success value.
|
|
16
|
+
*/
|
|
17
|
+
type Ok_<S> = Readonly<{
|
|
18
|
+
/** Discriminant property for the 'Ok' type. */
|
|
19
|
+
type: typeof OkTypeSymbol;
|
|
20
|
+
/** The success value. */
|
|
21
|
+
value: S;
|
|
22
|
+
}>;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @internal
|
|
26
|
+
* Represents the 'Err' variant of a Result, containing an error value.
|
|
27
|
+
* @template E The type of the error value.
|
|
28
|
+
*/
|
|
29
|
+
type Err_<E> = Readonly<{
|
|
30
|
+
/** Discriminant property for the 'Err' type. */
|
|
31
|
+
type: typeof ErrTypeSymbol;
|
|
32
|
+
/** The error value. */
|
|
33
|
+
value: E;
|
|
34
|
+
}>;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Represents a value that can either be a success (`Ok`) or an error (`Err`).
|
|
38
|
+
* @template S The type of the success value.
|
|
39
|
+
* @template E The type of the error value.
|
|
40
|
+
*/
|
|
41
|
+
export type Result<S, E> = Ok_<S> | Err_<E>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Namespace for the `Result` type and related functions.
|
|
45
|
+
* Provides utilities to handle operations that can succeed or fail.
|
|
46
|
+
*/
|
|
47
|
+
export namespace Result {
|
|
48
|
+
/**
|
|
49
|
+
* Checks if the given value is a `Result`.
|
|
50
|
+
* @param maybeOptional The value to check.
|
|
51
|
+
* @returns `true` if the value is a `Result`, otherwise `false`.
|
|
52
|
+
*/
|
|
53
|
+
export const isResult = (
|
|
54
|
+
maybeOptional: unknown,
|
|
55
|
+
): maybeOptional is Result<unknown, unknown> =>
|
|
56
|
+
isRecord(maybeOptional) &&
|
|
57
|
+
Object.hasOwn(maybeOptional, 'type') &&
|
|
58
|
+
Object.hasOwn(maybeOptional, 'value') &&
|
|
59
|
+
(maybeOptional['type'] === ErrTypeSymbol ||
|
|
60
|
+
maybeOptional['type'] === OkTypeSymbol);
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Represents a `Result` that is a success, containing a value.
|
|
64
|
+
* @template S The type of the success value.
|
|
65
|
+
*/
|
|
66
|
+
export type Ok<S> = Ok_<S>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Represents a `Result` that is an error, containing an error value.
|
|
70
|
+
* @template E The type of the error value.
|
|
71
|
+
*/
|
|
72
|
+
export type Err<E> = Err_<E>;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Base type for any `Result`, used for generic constraints.
|
|
76
|
+
* Represents a `Result` with unknown success and error types.
|
|
77
|
+
*/
|
|
78
|
+
export type Base = Result<unknown, unknown>;
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Extracts the success value type `S` from a `Result.Ok<S>`.
|
|
82
|
+
* If the `Result` is `Result.Err<E>`, resolves to `never`.
|
|
83
|
+
* @template R The `Result.Base` type to unwrap.
|
|
84
|
+
*/
|
|
85
|
+
export type UnwrapOk<R extends Base> = R extends Ok<infer S> ? S : never;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Extracts the error value type `E` from a `Result.Err<E>`.
|
|
89
|
+
* If the `Result` is `Result.Ok<S>`, resolves to `never`.
|
|
90
|
+
* @template R The `Result.Base` type to unwrap.
|
|
91
|
+
*/
|
|
92
|
+
export type UnwrapErr<R extends Base> = R extends Err<infer E> ? E : never;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Narrows a `Result.Base` type to `Result.Ok<S>` if it is an `Ok`.
|
|
96
|
+
* If the `Result` is `Result.Err<E>`, resolves to `never`.
|
|
97
|
+
* @template R The `Result.Base` type to narrow.
|
|
98
|
+
*/
|
|
99
|
+
export type NarrowToOk<R extends Base> = R extends Err<unknown> ? never : R;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Narrows a `Result.Base` type to `Result.Err<E>` if it is an `Err`.
|
|
103
|
+
* If the `Result` is `Result.Ok<S>`, resolves to `never`.
|
|
104
|
+
* @template R The `Result.Base` type to narrow.
|
|
105
|
+
*/
|
|
106
|
+
export type NarrowToErr<R extends Base> = R extends Ok<unknown> ? never : R;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Creates a `Result.Ok` containing the given success value.
|
|
110
|
+
*
|
|
111
|
+
* Use this constructor when an operation succeeds and you want to wrap
|
|
112
|
+
* the successful result in a Result type for consistent error handling.
|
|
113
|
+
*
|
|
114
|
+
* @template S The type of the success value.
|
|
115
|
+
* @param value The success value.
|
|
116
|
+
* @returns A `Result.Ok<S>` containing the value.
|
|
117
|
+
* @example
|
|
118
|
+
* ```typescript
|
|
119
|
+
* // Basic success case
|
|
120
|
+
* const success = Result.ok(42);
|
|
121
|
+
* console.log(Result.isOk(success)); // true
|
|
122
|
+
* console.log(Result.unwrapOk(success)); // 42
|
|
123
|
+
*
|
|
124
|
+
* // Function that returns a Result
|
|
125
|
+
* function divide(a: number, b: number): Result<number, string> {
|
|
126
|
+
* if (b === 0) {
|
|
127
|
+
* return Result.err("Division by zero");
|
|
128
|
+
* }
|
|
129
|
+
* return Result.ok(a / b);
|
|
130
|
+
* }
|
|
131
|
+
*
|
|
132
|
+
* const result = divide(10, 2);
|
|
133
|
+
* console.log(Result.unwrapOk(result)); // 5
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
export const ok = <S,>(value: S): Ok<S> => ({
|
|
137
|
+
type: OkTypeSymbol,
|
|
138
|
+
value,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Creates a `Result.Err` containing the given error value.
|
|
143
|
+
*
|
|
144
|
+
* Use this constructor when an operation fails and you want to wrap
|
|
145
|
+
* the error information in a Result type for consistent error handling.
|
|
146
|
+
*
|
|
147
|
+
* @template E The type of the error value.
|
|
148
|
+
* @param value The error value.
|
|
149
|
+
* @returns A `Result.Err<E>` containing the value.
|
|
150
|
+
* @example
|
|
151
|
+
* ```typescript
|
|
152
|
+
* // Basic error case
|
|
153
|
+
* const failure = Result.err("Something went wrong");
|
|
154
|
+
* console.log(Result.isErr(failure)); // true
|
|
155
|
+
* console.log(Result.unwrapErr(failure)); // "Something went wrong"
|
|
156
|
+
*
|
|
157
|
+
* // Function that can fail
|
|
158
|
+
* function parseInteger(input: string): Result<number, string> {
|
|
159
|
+
* const num = parseInt(input, 10);
|
|
160
|
+
* if (isNaN(num)) {
|
|
161
|
+
* return Result.err(`Invalid number format: ${input}`);
|
|
162
|
+
* }
|
|
163
|
+
* return Result.ok(num);
|
|
164
|
+
* }
|
|
165
|
+
*
|
|
166
|
+
* const result = parseInteger("abc");
|
|
167
|
+
* console.log(Result.unwrapErr(result)); // "Invalid number format: abc"
|
|
168
|
+
*
|
|
169
|
+
* // Using custom error types
|
|
170
|
+
* interface ValidationError {
|
|
171
|
+
* field: string;
|
|
172
|
+
* message: string;
|
|
173
|
+
* }
|
|
174
|
+
*
|
|
175
|
+
* const validationError = Result.err<ValidationError>({
|
|
176
|
+
* field: "email",
|
|
177
|
+
* message: "Invalid email format"
|
|
178
|
+
* });
|
|
179
|
+
* ```
|
|
180
|
+
*/
|
|
181
|
+
export const err = <E,>(value: E): Err<E> => ({
|
|
182
|
+
type: ErrTypeSymbol,
|
|
183
|
+
value,
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* @internal
|
|
188
|
+
* Default string conversion function using native String constructor.
|
|
189
|
+
*/
|
|
190
|
+
const toStr_ = String;
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Checks if a `Result` is `Result.Ok`.
|
|
194
|
+
* Acts as a type guard, narrowing the type to the success variant.
|
|
195
|
+
*
|
|
196
|
+
* This function is essential for type-safe Result handling, allowing
|
|
197
|
+
* TypeScript to understand that subsequent operations will work with
|
|
198
|
+
* the success value rather than the error value.
|
|
199
|
+
*
|
|
200
|
+
* @template R The `Result.Base` type to check.
|
|
201
|
+
* @param result The `Result` to check.
|
|
202
|
+
* @returns `true` if the `Result` is `Result.Ok`, otherwise `false`.
|
|
203
|
+
* @example
|
|
204
|
+
* ```typescript
|
|
205
|
+
* // Basic type guard usage
|
|
206
|
+
* const result: Result<number, string> = divide(10, 2);
|
|
207
|
+
*
|
|
208
|
+
* if (Result.isOk(result)) {
|
|
209
|
+
* // TypeScript knows result is Result.Ok<number>
|
|
210
|
+
* console.log(result.value); // Safe to access .value
|
|
211
|
+
* console.log(Result.unwrapOk(result)); // 5
|
|
212
|
+
* } else {
|
|
213
|
+
* // TypeScript knows result is Result.Err<string>
|
|
214
|
+
* console.log(result.value); // Error message
|
|
215
|
+
* }
|
|
216
|
+
*
|
|
217
|
+
* // Using in conditional logic
|
|
218
|
+
* const processResult = (r: Result<string, Error>) => {
|
|
219
|
+
* return Result.isOk(r)
|
|
220
|
+
* ? r.value.toUpperCase() // Safe string operations
|
|
221
|
+
* : "Error occurred";
|
|
222
|
+
* };
|
|
223
|
+
*
|
|
224
|
+
* // Filtering arrays of Results
|
|
225
|
+
* const results: Result<number, string>[] = [
|
|
226
|
+
* Result.ok(1),
|
|
227
|
+
* Result.err("error"),
|
|
228
|
+
* Result.ok(2)
|
|
229
|
+
* ];
|
|
230
|
+
* const successes = results.filter(Result.isOk);
|
|
231
|
+
* // successes is Result.Ok<number>[]
|
|
232
|
+
* ```
|
|
233
|
+
*/
|
|
234
|
+
export const isOk = <R extends Base>(result: R): result is NarrowToOk<R> =>
|
|
235
|
+
result.type === OkTypeSymbol;
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Checks if a `Result` is `Result.Err`.
|
|
239
|
+
* Acts as a type guard, narrowing the type to the error variant.
|
|
240
|
+
*
|
|
241
|
+
* This function is essential for type-safe Result handling, allowing
|
|
242
|
+
* TypeScript to understand that subsequent operations will work with
|
|
243
|
+
* the error value rather than the success value.
|
|
244
|
+
*
|
|
245
|
+
* @template R The `Result.Base` type to check.
|
|
246
|
+
* @param result The `Result` to check.
|
|
247
|
+
* @returns `true` if the `Result` is `Result.Err`, otherwise `false`.
|
|
248
|
+
* @example
|
|
249
|
+
* ```typescript
|
|
250
|
+
* // Basic type guard usage
|
|
251
|
+
* const result: Result<number, string> = divide(10, 0);
|
|
252
|
+
*
|
|
253
|
+
* if (Result.isErr(result)) {
|
|
254
|
+
* // TypeScript knows result is Result.Err<string>
|
|
255
|
+
* console.log(result.value); // Safe to access error .value
|
|
256
|
+
* console.log(Result.unwrapErr(result)); // "Division by zero"
|
|
257
|
+
* } else {
|
|
258
|
+
* // TypeScript knows result is Result.Ok<number>
|
|
259
|
+
* console.log(result.value); // Success value
|
|
260
|
+
* }
|
|
261
|
+
*
|
|
262
|
+
* // Error handling patterns
|
|
263
|
+
* const handleResult = (r: Result<Data, ApiError>) => {
|
|
264
|
+
* if (Result.isErr(r)) {
|
|
265
|
+
* logError(r.value); // Safe error operations
|
|
266
|
+
* return null;
|
|
267
|
+
* }
|
|
268
|
+
* return processData(r.value);
|
|
269
|
+
* };
|
|
270
|
+
*
|
|
271
|
+
* // Collecting errors from multiple Results
|
|
272
|
+
* const results: Result<string, ValidationError>[] = validateForm();
|
|
273
|
+
* const errors = results
|
|
274
|
+
* .filter(Result.isErr)
|
|
275
|
+
* .map(err => err.value); // ValidationError[]
|
|
276
|
+
* ```
|
|
277
|
+
*/
|
|
278
|
+
export const isErr = <R extends Base>(result: R): result is NarrowToErr<R> =>
|
|
279
|
+
result.type === ErrTypeSymbol;
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Unwraps a `Result`, returning the success value.
|
|
283
|
+
* Throws an error if the `Result` is `Result.Err`.
|
|
284
|
+
*
|
|
285
|
+
* This is useful when you're confident that a Result should contain a success value
|
|
286
|
+
* and want to treat errors as exceptional conditions. The error message will be
|
|
287
|
+
* constructed from the error value using the provided string conversion function.
|
|
288
|
+
*
|
|
289
|
+
* @template R The `Result.Base` type to unwrap.
|
|
290
|
+
* @param result The `Result` to unwrap.
|
|
291
|
+
* @param toStr An optional function to convert the error value to a string for the error message. Defaults to `String`.
|
|
292
|
+
* @returns The success value if `Result.Ok`.
|
|
293
|
+
* @throws {Error} Error with the stringified error value if the `Result` is `Result.Err`.
|
|
294
|
+
* @example
|
|
295
|
+
* ```typescript
|
|
296
|
+
* // Basic usage with default string conversion
|
|
297
|
+
* const success = Result.ok(42);
|
|
298
|
+
* console.log(Result.unwrapThrow(success)); // 42
|
|
299
|
+
*
|
|
300
|
+
* const failure = Result.err("Network error");
|
|
301
|
+
* try {
|
|
302
|
+
* Result.unwrapThrow(failure); // throws Error: "Network error"
|
|
303
|
+
* } catch (error) {
|
|
304
|
+
* console.log(error.message); // "Network error"
|
|
305
|
+
* }
|
|
306
|
+
*
|
|
307
|
+
* // Custom error string conversion
|
|
308
|
+
* interface ApiError {
|
|
309
|
+
* code: number;
|
|
310
|
+
* message: string;
|
|
311
|
+
* }
|
|
312
|
+
*
|
|
313
|
+
* const apiResult = Result.err<ApiError>({ code: 404, message: "Not found" });
|
|
314
|
+
* try {
|
|
315
|
+
* Result.unwrapThrow(apiResult, err => `API Error ${err.code}: ${err.message}`);
|
|
316
|
+
* } catch (error) {
|
|
317
|
+
* console.log(error.message); // "API Error 404: Not found"
|
|
318
|
+
* }
|
|
319
|
+
*
|
|
320
|
+
* // In contexts where failure is unexpected
|
|
321
|
+
* const configResult = loadConfiguration();
|
|
322
|
+
* const config = Result.unwrapThrow(configResult, err =>
|
|
323
|
+
* `Failed to load configuration: ${err}`
|
|
324
|
+
* ); // Will throw if config loading fails
|
|
325
|
+
* ```
|
|
326
|
+
*/
|
|
327
|
+
export const unwrapThrow = <R extends Base>(
|
|
328
|
+
result: R,
|
|
329
|
+
toStr: (e: UnwrapErr<R>) => string = toStr_,
|
|
330
|
+
): UnwrapOk<R> => {
|
|
331
|
+
if (isErr(result)) {
|
|
332
|
+
throw new Error(toStr(result.value as UnwrapErr<R>));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return result.value as UnwrapOk<R>;
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Unwraps a `Result`, returning the success value or `undefined` if it's an error.
|
|
340
|
+
*
|
|
341
|
+
* This function provides a safe way to extract success values from Results without
|
|
342
|
+
* throwing exceptions. It has overloaded behavior based on the type:
|
|
343
|
+
* - For `Result.Ok<T>`: Always returns `T` (guaranteed by type system)
|
|
344
|
+
* - For general `Result<T, E>`: Returns `T | undefined`
|
|
345
|
+
*
|
|
346
|
+
* @template R The `Result.Base` type to unwrap.
|
|
347
|
+
* @param result The `Result` to unwrap.
|
|
348
|
+
* @returns The success value if `Result.Ok`, otherwise `undefined`.
|
|
349
|
+
* @example
|
|
350
|
+
* ```typescript
|
|
351
|
+
* // With guaranteed Ok - returns the value
|
|
352
|
+
* const success = Result.ok(42);
|
|
353
|
+
* const value = Result.unwrapOk(success); // Type: number, Value: 42
|
|
354
|
+
*
|
|
355
|
+
* // With general Result - may return undefined
|
|
356
|
+
* const maybeResult: Result<string, Error> = fetchData();
|
|
357
|
+
* const data = Result.unwrapOk(maybeResult); // Type: string | undefined
|
|
358
|
+
*
|
|
359
|
+
* // Safe pattern for handling both cases
|
|
360
|
+
* const result = Result.ok("hello");
|
|
361
|
+
* const unwrapped = Result.unwrapOk(result);
|
|
362
|
+
* if (unwrapped !== undefined) {
|
|
363
|
+
* console.log(unwrapped.toUpperCase()); // "HELLO"
|
|
364
|
+
* }
|
|
365
|
+
*
|
|
366
|
+
* // Useful in conditional chains
|
|
367
|
+
* const processResult = (r: Result<number, string>) => {
|
|
368
|
+
* const value = Result.unwrapOk(r);
|
|
369
|
+
* return value !== undefined ? value * 2 : 0;
|
|
370
|
+
* };
|
|
371
|
+
* ```
|
|
372
|
+
*/
|
|
373
|
+
export const unwrapOk: UnwrapOkFnOverload = (<R extends Base>(
|
|
374
|
+
result: R,
|
|
375
|
+
): UnwrapOk<R> | undefined =>
|
|
376
|
+
isErr(result)
|
|
377
|
+
? undefined
|
|
378
|
+
: (result.value as UnwrapOk<R>)) as UnwrapOkFnOverload;
|
|
379
|
+
|
|
380
|
+
type UnwrapOkFnOverload = {
|
|
381
|
+
<R extends Ok<unknown>>(result: R): UnwrapOk<R>;
|
|
382
|
+
|
|
383
|
+
// Curried version
|
|
384
|
+
<R extends Base>(result: R): UnwrapOk<R> | undefined;
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Unwraps a `Result`, returning the success value or a default value if it is `Result.Err`.
|
|
389
|
+
* @template R The `Result.Base` type to unwrap.
|
|
390
|
+
* @template D The type of the default value.
|
|
391
|
+
* @param result The `Result` to unwrap.
|
|
392
|
+
* @param defaultValue The value to return if `result` is `Result.Err`.
|
|
393
|
+
* @returns The success value if `Result.Ok`, otherwise `defaultValue`.
|
|
394
|
+
* @example
|
|
395
|
+
* ```typescript
|
|
396
|
+
* // Regular usage
|
|
397
|
+
* const result = Result.ok(42);
|
|
398
|
+
* const value = Result.unwrapOkOr(result, 0);
|
|
399
|
+
* console.log(value); // 42
|
|
400
|
+
*
|
|
401
|
+
* // Curried usage for pipe composition
|
|
402
|
+
* const unwrapWithDefault = Result.unwrapOkOr(0);
|
|
403
|
+
* const value2 = pipe(Result.err("error")).map(unwrapWithDefault).value;
|
|
404
|
+
* console.log(value2); // 0
|
|
405
|
+
* ```
|
|
406
|
+
*/
|
|
407
|
+
export const unwrapOkOr: UnwrapOkOrFnOverload = (<R extends Base, D>(
|
|
408
|
+
...args: readonly [result: R, defaultValue: D] | readonly [defaultValue: D]
|
|
409
|
+
):
|
|
410
|
+
| D
|
|
411
|
+
| UnwrapOk<R>
|
|
412
|
+
| (<E>(result: Result<UnwrapOk<R>, E>) => D | UnwrapOk<R>) => {
|
|
413
|
+
switch (args.length) {
|
|
414
|
+
case 2: {
|
|
415
|
+
// Direct version: first argument is result
|
|
416
|
+
const [result, defaultValue] = args;
|
|
417
|
+
return isErr(result) ? defaultValue : (result.value as UnwrapOk<R>);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
case 1: {
|
|
421
|
+
// Curried version: first argument is default value
|
|
422
|
+
const [defaultValue] = args;
|
|
423
|
+
return <E,>(result: Result<UnwrapOk<R>, E>) =>
|
|
424
|
+
unwrapOkOr(result, defaultValue);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}) as UnwrapOkOrFnOverload;
|
|
428
|
+
|
|
429
|
+
type UnwrapOkOrFnOverload = {
|
|
430
|
+
<R extends Base, D>(result: R, defaultValue: D): D | UnwrapOk<R>;
|
|
431
|
+
|
|
432
|
+
// Curried version
|
|
433
|
+
<S, D>(defaultValue: D): <E>(result: Result<S, E>) => D | S;
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Unwraps a `Result`, returning the error value.
|
|
438
|
+
* Throws an error if the `Result` is `Result.Ok`.
|
|
439
|
+
*
|
|
440
|
+
* This function is used when you expect a Result to be an error and want to
|
|
441
|
+
* extract the error value. If the Result is unexpectedly Ok, it will throw
|
|
442
|
+
* an error with information about the unexpected success value.
|
|
443
|
+
*
|
|
444
|
+
* @template R The `Result.Base` type to unwrap.
|
|
445
|
+
* @param result The `Result` to unwrap.
|
|
446
|
+
* @param toStr An optional function to convert the success value to a string for the error message when the Result is unexpectedly Ok. Defaults to `String`.
|
|
447
|
+
* @returns The error value if `Result.Err`.
|
|
448
|
+
* @throws {Error} Error with message "Expected Err but got Ok: {value}" if the `Result` is `Result.Ok`.
|
|
449
|
+
* @example
|
|
450
|
+
* ```typescript
|
|
451
|
+
* // Basic usage - extracting error from known failure
|
|
452
|
+
* const failure = Result.err("Network timeout");
|
|
453
|
+
* console.log(Result.unwrapErrThrow(failure)); // "Network timeout"
|
|
454
|
+
*
|
|
455
|
+
* // Throws when Result is unexpectedly Ok
|
|
456
|
+
* const success = Result.ok(42);
|
|
457
|
+
* try {
|
|
458
|
+
* Result.unwrapErrThrow(success); // throws Error: "Expected Err but got Ok: 42"
|
|
459
|
+
* } catch (error) {
|
|
460
|
+
* console.log(error.message); // "Expected Err but got Ok: 42"
|
|
461
|
+
* }
|
|
462
|
+
*
|
|
463
|
+
* // Custom success value string conversion
|
|
464
|
+
* interface User { name: string; id: number; }
|
|
465
|
+
* const userResult = Result.ok<User>({ name: "John", id: 123 });
|
|
466
|
+
* try {
|
|
467
|
+
* Result.unwrapErrThrow(userResult, user => `User(${user.name}:${user.id})`);
|
|
468
|
+
* } catch (error) {
|
|
469
|
+
* console.log(error.message); // "Expected Err but got Ok: User(John:123)"
|
|
470
|
+
* }
|
|
471
|
+
*
|
|
472
|
+
* // In error handling contexts
|
|
473
|
+
* const validateAndGetError = (result: Result<any, ValidationError>) => {
|
|
474
|
+
* if (Result.isErr(result)) {
|
|
475
|
+
* return Result.unwrapErrThrow(result); // Safe to unwrap error
|
|
476
|
+
* }
|
|
477
|
+
* throw new Error("Validation unexpectedly succeeded");
|
|
478
|
+
* };
|
|
479
|
+
* ```
|
|
480
|
+
*/
|
|
481
|
+
export const unwrapErrThrow = <R extends Base>(
|
|
482
|
+
result: R,
|
|
483
|
+
toStr: (v: UnwrapOk<R>) => string = toStr_,
|
|
484
|
+
): UnwrapErr<R> => {
|
|
485
|
+
if (isOk(result)) {
|
|
486
|
+
throw new Error(
|
|
487
|
+
`Expected Err but got Ok: ${toStr(result.value as UnwrapOk<R>)}`,
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
return result.value as UnwrapErr<R>;
|
|
492
|
+
};
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Unwraps a `Result`, returning the error value or `undefined` if it is `Result.Ok`.
|
|
496
|
+
*
|
|
497
|
+
* This provides a safe way to extract error values from Results without throwing
|
|
498
|
+
* exceptions. Useful for error handling patterns where you want to check for
|
|
499
|
+
* specific error conditions.
|
|
500
|
+
*
|
|
501
|
+
* @template R The `Result.Base` type to unwrap.
|
|
502
|
+
* @param result The `Result` to unwrap.
|
|
503
|
+
* @returns The error value if `Result.Err`, otherwise `undefined`.
|
|
504
|
+
* @example
|
|
505
|
+
* ```typescript
|
|
506
|
+
* // Basic error extraction
|
|
507
|
+
* const failure = Result.err("Connection failed");
|
|
508
|
+
* console.log(Result.unwrapErr(failure)); // "Connection failed"
|
|
509
|
+
*
|
|
510
|
+
* const success = Result.ok(42);
|
|
511
|
+
* console.log(Result.unwrapErr(success)); // undefined
|
|
512
|
+
*
|
|
513
|
+
* // Error handling patterns
|
|
514
|
+
* const handleApiCall = (result: Result<Data, ApiError>) => {
|
|
515
|
+
* const error = Result.unwrapErr(result);
|
|
516
|
+
* if (error !== undefined) {
|
|
517
|
+
* switch (error.type) {
|
|
518
|
+
* case "NETWORK_ERROR":
|
|
519
|
+
* return retry(result);
|
|
520
|
+
* case "AUTH_ERROR":
|
|
521
|
+
* return redirectToLogin();
|
|
522
|
+
* default:
|
|
523
|
+
* return showGenericError(error);
|
|
524
|
+
* }
|
|
525
|
+
* }
|
|
526
|
+
* // Handle success case...
|
|
527
|
+
* };
|
|
528
|
+
*
|
|
529
|
+
* // Collecting errors from multiple operations
|
|
530
|
+
* const results = await Promise.all([
|
|
531
|
+
* operation1(),
|
|
532
|
+
* operation2(),
|
|
533
|
+
* operation3()
|
|
534
|
+
* ]);
|
|
535
|
+
*
|
|
536
|
+
* const errors = results
|
|
537
|
+
* .map(Result.unwrapErr)
|
|
538
|
+
* .filter(err => err !== undefined); // Only actual errors
|
|
539
|
+
* ```
|
|
540
|
+
*/
|
|
541
|
+
export const unwrapErr = <R extends Base>(
|
|
542
|
+
result: R,
|
|
543
|
+
): UnwrapErr<R> | undefined =>
|
|
544
|
+
isErr(result) ? (result.value as UnwrapErr<R>) : undefined;
|
|
545
|
+
|
|
546
|
+
/**
|
|
547
|
+
* Unwraps a `Result`, returning the error value or a default value if it is `Result.Ok`.
|
|
548
|
+
* @template R The `Result.Base` type to unwrap.
|
|
549
|
+
* @template D The type of the default value.
|
|
550
|
+
* @param result The `Result` to unwrap.
|
|
551
|
+
* @param defaultValue The value to return if `result` is `Result.Ok`.
|
|
552
|
+
* @returns The error value if `Result.Err`, otherwise `defaultValue`.
|
|
553
|
+
* @example
|
|
554
|
+
* ```typescript
|
|
555
|
+
* // Regular usage
|
|
556
|
+
* const result = Result.err("failed");
|
|
557
|
+
* const error = Result.unwrapErrOr(result, "default");
|
|
558
|
+
* console.log(error); // "failed"
|
|
559
|
+
*
|
|
560
|
+
* // Curried usage for pipe composition
|
|
561
|
+
* const unwrapErrorWithDefault = Result.unwrapErrOr("unknown error");
|
|
562
|
+
* const error2 = pipe(Result.ok(42)).map(unwrapErrorWithDefault).value;
|
|
563
|
+
* console.log(error2); // "unknown error"
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
566
|
+
export const unwrapErrOr: UnwrapErrOrFnOverload = (<R extends Base, D>(
|
|
567
|
+
...args: readonly [result: R, defaultValue: D] | readonly [defaultValue: D]
|
|
568
|
+
):
|
|
569
|
+
| D
|
|
570
|
+
| UnwrapErr<R>
|
|
571
|
+
| (<S>(result: Result<S, UnwrapErr<R>>) => D | UnwrapErr<R>) => {
|
|
572
|
+
switch (args.length) {
|
|
573
|
+
case 2: {
|
|
574
|
+
// Direct version: first argument is result
|
|
575
|
+
const [result, defaultValue] = args;
|
|
576
|
+
return isErr(result) ? (result.value as UnwrapErr<R>) : defaultValue;
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
case 1: {
|
|
580
|
+
// Curried version: first argument is default value
|
|
581
|
+
const [defaultValue] = args;
|
|
582
|
+
return <S,>(result: Result<S, UnwrapErr<R>>) =>
|
|
583
|
+
unwrapErrOr(result, defaultValue);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
}) as UnwrapErrOrFnOverload;
|
|
587
|
+
|
|
588
|
+
type UnwrapErrOrFnOverload = {
|
|
589
|
+
<R extends Base, D>(result: R, defaultValue: D): D | UnwrapErr<R>;
|
|
590
|
+
|
|
591
|
+
// Curried version
|
|
592
|
+
<E, D>(defaultValue: D): <S>(result: Result<S, E>) => D | E;
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Maps a `Result<S, E>` to `Result<S2, E>` by applying a function to the success value.
|
|
597
|
+
* If the `Result` is `Result.Err`, returns the original `Err`.
|
|
598
|
+
* @template R The input `Result.Base` type.
|
|
599
|
+
* @template S2 The type of the success value returned by the mapping function.
|
|
600
|
+
* @param result The `Result` to map.
|
|
601
|
+
* @param mapFn The function to apply to the success value if present.
|
|
602
|
+
* @returns A new `Result<S2, UnwrapErr<R>>`.
|
|
603
|
+
* @example
|
|
604
|
+
* ```typescript
|
|
605
|
+
* // Regular usage
|
|
606
|
+
* const result = Result.ok(5);
|
|
607
|
+
* const mapped = Result.map(result, x => x * 2);
|
|
608
|
+
* console.log(Result.unwrap(mapped)); // 10
|
|
609
|
+
*
|
|
610
|
+
* // Curried version for use with pipe
|
|
611
|
+
* const doubler = Result.map((x: number) => x * 2);
|
|
612
|
+
* const result2 = pipe(Result.ok(5)).map(doubler).value;
|
|
613
|
+
* console.log(Result.unwrap(result2)); // 10
|
|
614
|
+
* ```
|
|
615
|
+
*/
|
|
616
|
+
export const map: MapFnOverload = (<R extends Base, S2>(
|
|
617
|
+
...args:
|
|
618
|
+
| readonly [result: R, mapFn: (value: UnwrapOk<R>) => S2]
|
|
619
|
+
| readonly [mapFn: (value: UnwrapOk<R>) => S2]
|
|
620
|
+
): Result<S2, UnwrapErr<R>> | ((result: R) => Result<S2, UnwrapErr<R>>) => {
|
|
621
|
+
switch (args.length) {
|
|
622
|
+
case 2: {
|
|
623
|
+
const [result, mapFn] = args;
|
|
624
|
+
return isErr(result)
|
|
625
|
+
? (result as Err<UnwrapErr<R>>)
|
|
626
|
+
: ok(mapFn(result.value as UnwrapOk<R>));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
case 1: {
|
|
630
|
+
// Curried version: first argument is mapping function
|
|
631
|
+
const [mapFn] = args;
|
|
632
|
+
return (result: R) => map(result, mapFn);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}) as MapFnOverload;
|
|
636
|
+
|
|
637
|
+
type MapFnOverload = {
|
|
638
|
+
<R extends Base, S2>(
|
|
639
|
+
result: R,
|
|
640
|
+
mapFn: (value: UnwrapOk<R>) => S2,
|
|
641
|
+
): Result<S2, UnwrapErr<R>>;
|
|
642
|
+
|
|
643
|
+
// Curried version
|
|
644
|
+
<S, S2>(
|
|
645
|
+
mapFn: (value: S) => S2,
|
|
646
|
+
): <E>(result: Result<S, E>) => Result<S2, E>;
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Maps a `Result<S, E>` to `Result<S, E2>` by applying a function to the error value.
|
|
651
|
+
* If the `Result` is `Result.Ok`, returns the original `Ok`.
|
|
652
|
+
* @template R The input `Result.Base` type.
|
|
653
|
+
* @template E2 The type of the error value returned by the mapping function.
|
|
654
|
+
* @param result The `Result` to map.
|
|
655
|
+
* @param mapFn The function to apply to the error value if present.
|
|
656
|
+
* @returns A new `Result<UnwrapOk<R>, E2>`.
|
|
657
|
+
* @example
|
|
658
|
+
* ```typescript
|
|
659
|
+
* // Regular usage
|
|
660
|
+
* const result = Result.err("error");
|
|
661
|
+
* const mapped = Result.mapErr(result, e => e.toUpperCase());
|
|
662
|
+
* console.log(Result.unwrapErr(mapped)); // "ERROR"
|
|
663
|
+
*
|
|
664
|
+
* // Curried usage for pipe composition
|
|
665
|
+
* const errorUppercase = Result.mapErr((e: string) => e.toUpperCase());
|
|
666
|
+
* const result2 = pipe(Result.err("error")).map(errorUppercase).value;
|
|
667
|
+
* console.log(Result.unwrapErr(result2)); // "ERROR"
|
|
668
|
+
* ```
|
|
669
|
+
*/
|
|
670
|
+
export const mapErr: MapErrFnOverload = (<R extends Base, E2>(
|
|
671
|
+
...args:
|
|
672
|
+
| readonly [result: R, mapFn: (error: UnwrapErr<R>) => E2]
|
|
673
|
+
| readonly [mapFn: (error: UnwrapErr<R>) => E2]
|
|
674
|
+
): Result<UnwrapOk<R>, E2> | ((result: R) => Result<UnwrapOk<R>, E2>) => {
|
|
675
|
+
switch (args.length) {
|
|
676
|
+
case 2: {
|
|
677
|
+
const [result, mapFn] = args;
|
|
678
|
+
return isOk(result)
|
|
679
|
+
? (result as Ok<UnwrapOk<R>>)
|
|
680
|
+
: err(mapFn(result.value as UnwrapErr<R>));
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
case 1: {
|
|
684
|
+
// Curried version: first argument is mapping function
|
|
685
|
+
const [mapFn] = args;
|
|
686
|
+
return (result: R) => mapErr(result, mapFn);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
}) as MapErrFnOverload;
|
|
690
|
+
|
|
691
|
+
type MapErrFnOverload = {
|
|
692
|
+
<R extends Base, E2>(
|
|
693
|
+
result: R,
|
|
694
|
+
mapFn: (error: UnwrapErr<R>) => E2,
|
|
695
|
+
): Result<UnwrapOk<R>, E2>;
|
|
696
|
+
|
|
697
|
+
// Curried version
|
|
698
|
+
<E, E2>(
|
|
699
|
+
mapFn: (error: E) => E2,
|
|
700
|
+
): <S>(result: Result<S, E>) => Result<S, E2>;
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
/**
|
|
704
|
+
* Applies one of two functions depending on whether the `Result` is `Ok` or `Err`.
|
|
705
|
+
* @template R The input `Result.Base` type.
|
|
706
|
+
* @template S2 The type of the success value returned by `mapFn`.
|
|
707
|
+
* @template E2 The type of the error value returned by `mapErrFn`.
|
|
708
|
+
* @param result The `Result` to fold.
|
|
709
|
+
* @param mapFn The function to apply if `result` is `Ok`.
|
|
710
|
+
* @param mapErrFn The function to apply if `result` is `Err`.
|
|
711
|
+
* @returns A new `Result<S2, E2>` based on the applied function.
|
|
712
|
+
* @example
|
|
713
|
+
* ```typescript
|
|
714
|
+
* // Regular usage
|
|
715
|
+
* const result = Result.ok(42);
|
|
716
|
+
* const folded = Result.fold(result, x => x * 2, () => 0);
|
|
717
|
+
* console.log(Result.unwrapOk(folded)); // 84
|
|
718
|
+
*
|
|
719
|
+
* // Curried usage for pipe composition
|
|
720
|
+
* const folder = Result.fold((x: number) => x * 2, () => 0);
|
|
721
|
+
* const result2 = pipe(Result.ok(42)).map(folder).value;
|
|
722
|
+
* console.log(Result.unwrapOk(result2)); // 84
|
|
723
|
+
* ```
|
|
724
|
+
*/
|
|
725
|
+
export const fold: FoldFnOverload = (<R extends Base, S2, E2>(
|
|
726
|
+
...args:
|
|
727
|
+
| readonly [
|
|
728
|
+
result: R,
|
|
729
|
+
mapFn: (value: UnwrapOk<R>) => S2,
|
|
730
|
+
mapErrFn: (error: UnwrapErr<R>) => E2,
|
|
731
|
+
]
|
|
732
|
+
| readonly [
|
|
733
|
+
mapFn: (value: UnwrapOk<R>) => S2,
|
|
734
|
+
mapErrFn: (error: UnwrapErr<R>) => E2,
|
|
735
|
+
]
|
|
736
|
+
):
|
|
737
|
+
| Result<S2, E2>
|
|
738
|
+
| ((result: Result<UnwrapOk<R>, UnwrapErr<R>>) => Result<S2, E2>) => {
|
|
739
|
+
switch (args.length) {
|
|
740
|
+
case 3: {
|
|
741
|
+
const [result, mapFn, mapErrFn] = args;
|
|
742
|
+
return isOk(result)
|
|
743
|
+
? ok(mapFn(result.value as UnwrapOk<R>))
|
|
744
|
+
: err(mapErrFn(result.value as UnwrapErr<R>));
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
case 2: {
|
|
748
|
+
const [mapFn, mapErrFn] = args;
|
|
749
|
+
return (result: Result<UnwrapOk<R>, UnwrapErr<R>>) =>
|
|
750
|
+
isOk(result) ? ok(mapFn(result.value)) : err(mapErrFn(result.value));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
}) as FoldFnOverload;
|
|
754
|
+
|
|
755
|
+
type FoldFnOverload = {
|
|
756
|
+
<R extends Base, S2, E2>(
|
|
757
|
+
result: R,
|
|
758
|
+
mapFn: (value: UnwrapOk<R>) => S2,
|
|
759
|
+
mapErrFn: (error: UnwrapErr<R>) => E2,
|
|
760
|
+
): Result<S2, E2>;
|
|
761
|
+
|
|
762
|
+
// Curried version
|
|
763
|
+
<S, E, S2, E2>(
|
|
764
|
+
mapFn: (value: S) => S2,
|
|
765
|
+
mapErrFn: (error: E) => E2,
|
|
766
|
+
): (result: Result<S, E>) => Result<S2, E2>;
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
/**
|
|
770
|
+
* Applies a function that returns a `Result` to the success value of a `Result`.
|
|
771
|
+
* If the input is `Err`, returns the original `Err`.
|
|
772
|
+
* This is the monadic bind operation for `Result`.
|
|
773
|
+
* @template R The input `Result.Base` type.
|
|
774
|
+
* @template S2 The success type of the `Result` returned by the function.
|
|
775
|
+
* @template E2 The error type of the `Result` returned by the function.
|
|
776
|
+
* @param result The `Result` to flat map.
|
|
777
|
+
* @param flatMapFn The function to apply that returns a `Result`.
|
|
778
|
+
* @returns The result of applying the function, or the original `Err`.
|
|
779
|
+
* @example
|
|
780
|
+
* ```typescript
|
|
781
|
+
* // Regular usage
|
|
782
|
+
* const divide = (a: number, b: number): Result<number, string> =>
|
|
783
|
+
* b === 0 ? Result.err("Division by zero") : Result.ok(a / b);
|
|
784
|
+
*
|
|
785
|
+
* const result = Result.flatMap(Result.ok(10), x => divide(x, 2));
|
|
786
|
+
* console.log(Result.unwrapOk(result)); // 5
|
|
787
|
+
*
|
|
788
|
+
* // Curried usage for pipe composition
|
|
789
|
+
* const divideBy2 = Result.flatMap((x: number) => divide(x, 2));
|
|
790
|
+
* const result2 = pipe(Result.ok(10)).map(divideBy2).value;
|
|
791
|
+
* console.log(Result.unwrapOk(result2)); // 5
|
|
792
|
+
* ```
|
|
793
|
+
*/
|
|
794
|
+
export const flatMap: FlatMapFnOverload = (<R extends Base, S2, E2>(
|
|
795
|
+
...args:
|
|
796
|
+
| readonly [result: R, flatMapFn: (value: UnwrapOk<R>) => Result<S2, E2>]
|
|
797
|
+
| readonly [flatMapFn: (value: UnwrapOk<R>) => Result<S2, E2>]
|
|
798
|
+
):
|
|
799
|
+
| Result<S2, E2 | UnwrapErr<R>>
|
|
800
|
+
| (<E>(result: Result<UnwrapOk<R>, E>) => Result<S2, E | E2>) => {
|
|
801
|
+
switch (args.length) {
|
|
802
|
+
case 2: {
|
|
803
|
+
const [result, flatMapFn] = args;
|
|
804
|
+
return isErr(result)
|
|
805
|
+
? (result as Err<UnwrapErr<R>>)
|
|
806
|
+
: flatMapFn(result.value as UnwrapOk<R>);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
case 1: {
|
|
810
|
+
const [flatMapFn] = args;
|
|
811
|
+
return <E,>(result: Result<UnwrapOk<R>, E>) =>
|
|
812
|
+
isErr(result) ? result : flatMapFn(result.value);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}) as FlatMapFnOverload;
|
|
816
|
+
|
|
817
|
+
type FlatMapFnOverload = {
|
|
818
|
+
<R extends Base, S2, E2>(
|
|
819
|
+
result: R,
|
|
820
|
+
flatMapFn: (value: UnwrapOk<R>) => Result<S2, E2>,
|
|
821
|
+
): Result<S2, E2 | UnwrapErr<R>>;
|
|
822
|
+
|
|
823
|
+
// Curried version
|
|
824
|
+
<S, S2, E2>(
|
|
825
|
+
flatMapFn: (value: S) => Result<S2, E2>,
|
|
826
|
+
): <E>(result: Result<S, E>) => Result<S2, E | E2>;
|
|
827
|
+
};
|
|
828
|
+
|
|
829
|
+
/**
|
|
830
|
+
* Unwraps a `Result`, returning the success value or throwing an error with the provided message.
|
|
831
|
+
* @template R The `Result.Base` type to unwrap.
|
|
832
|
+
* @param result The `Result` to unwrap.
|
|
833
|
+
* @param message The error message to throw if the `Result` is `Result.Err`.
|
|
834
|
+
* @returns The success value if `Result.Ok`.
|
|
835
|
+
* @throws Error with the provided message if the `Result` is `Result.Err`.
|
|
836
|
+
* @example
|
|
837
|
+
* ```typescript
|
|
838
|
+
* // Regular usage
|
|
839
|
+
* const result = Result.ok(42);
|
|
840
|
+
* const value = Result.expectToBe(result, "Operation must succeed");
|
|
841
|
+
* console.log(value); // 42
|
|
842
|
+
*
|
|
843
|
+
* // Curried usage for pipe composition
|
|
844
|
+
* const mustBeOk = Result.expectToBe("Operation must succeed");
|
|
845
|
+
* const value2 = pipe(Result.ok(42)).map(mustBeOk).value;
|
|
846
|
+
* console.log(value2); // 42
|
|
847
|
+
* ```
|
|
848
|
+
*/
|
|
849
|
+
export const expectToBe: ExpectToBeFnOverload = (<R extends Base>(
|
|
850
|
+
...args: readonly [result: R, message: string] | readonly [message: string]
|
|
851
|
+
): UnwrapOk<R> | (<E>(result: Result<UnwrapOk<R>, E>) => UnwrapOk<R>) => {
|
|
852
|
+
switch (args.length) {
|
|
853
|
+
case 2: {
|
|
854
|
+
// Direct version: first argument is result
|
|
855
|
+
const [result, message] = args;
|
|
856
|
+
if (isOk(result)) {
|
|
857
|
+
return unwrapOk(result);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
throw new Error(message);
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
case 1: {
|
|
864
|
+
// Curried version: first argument is message
|
|
865
|
+
const [message] = args;
|
|
866
|
+
return <E,>(result: Result<UnwrapOk<R>, E>): UnwrapOk<R> =>
|
|
867
|
+
expectToBe(result, message);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
}) as ExpectToBeFnOverload;
|
|
871
|
+
|
|
872
|
+
type ExpectToBeFnOverload = {
|
|
873
|
+
<R extends Base>(result: R, message: string): UnwrapOk<R>;
|
|
874
|
+
|
|
875
|
+
// Curried version
|
|
876
|
+
<S>(message: string): <E>(result: Result<S, E>) => S;
|
|
877
|
+
};
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* @internal
|
|
881
|
+
* Utility type to extract the resolved value type from a Promise.
|
|
882
|
+
* @template P The Promise type.
|
|
883
|
+
* @example
|
|
884
|
+
* UnwrapPromise<Promise<string>> // string
|
|
885
|
+
* UnwrapPromise<Promise<number>> // number
|
|
886
|
+
*/
|
|
887
|
+
type UnwrapPromise<P extends Promise<unknown>> =
|
|
888
|
+
P extends Promise<infer V> ? V : never;
|
|
889
|
+
|
|
890
|
+
/**
|
|
891
|
+
* Converts a Promise into a Promise that resolves to a `Result`.
|
|
892
|
+
* If the input Promise resolves, the `Result` will be `Ok` with the resolved value.
|
|
893
|
+
* If the input Promise rejects, the `Result` will be `Err` with the rejection reason.
|
|
894
|
+
* @template P The type of the input Promise.
|
|
895
|
+
* @param promise The Promise to convert.
|
|
896
|
+
* @returns A Promise that resolves to `Result<UnwrapPromise<P>, unknown>`.
|
|
897
|
+
*/
|
|
898
|
+
export const fromPromise = <P extends Promise<unknown>>(
|
|
899
|
+
promise: P,
|
|
900
|
+
): Promise<Result<UnwrapPromise<P>, unknown>> =>
|
|
901
|
+
promise.then((v) => ok(v) as Ok<UnwrapPromise<P>>).catch(err);
|
|
902
|
+
|
|
903
|
+
/**
|
|
904
|
+
* Wraps a function that may throw an exception in a `Result`.
|
|
905
|
+
*
|
|
906
|
+
* This is a fundamental utility for converting traditional exception-based error
|
|
907
|
+
* handling into Result-based error handling. Any thrown value is converted to an
|
|
908
|
+
* Error object for consistent error handling.
|
|
909
|
+
*
|
|
910
|
+
* If the function executes successfully, returns `Result.Ok` with the result.
|
|
911
|
+
* If the function throws, returns `Result.Err` with the caught error.
|
|
912
|
+
*
|
|
913
|
+
* @template T The return type of the function.
|
|
914
|
+
* @param fn The function to execute that may throw.
|
|
915
|
+
* @returns A `Result<T, Error>` containing either the successful result or the caught error.
|
|
916
|
+
* @example
|
|
917
|
+
* ```typescript
|
|
918
|
+
* // Wrapping JSON.parse which can throw
|
|
919
|
+
* const parseJson = <T>(text: string): Result<T, Error> =>
|
|
920
|
+
* Result.fromThrowable(() => JSON.parse(text) as T);
|
|
921
|
+
*
|
|
922
|
+
* const validJson = parseJson<{valid: boolean}>('{"valid": true}');
|
|
923
|
+
* if (Result.isOk(validJson)) {
|
|
924
|
+
* console.log(validJson.value.valid); // true
|
|
925
|
+
* }
|
|
926
|
+
*
|
|
927
|
+
* const invalidJson = parseJson('{invalid json}');
|
|
928
|
+
* if (Result.isErr(invalidJson)) {
|
|
929
|
+
* console.log(invalidJson.value.message); // SyntaxError message
|
|
930
|
+
* }
|
|
931
|
+
*
|
|
932
|
+
* // Using with custom validation
|
|
933
|
+
* const parsePositiveNumber = (str: string): Result<number, Error> =>
|
|
934
|
+
* Result.fromThrowable(() => {
|
|
935
|
+
* const num = Number(str);
|
|
936
|
+
* if (Number.isNaN(num)) throw new Error(`Not a number: ${str}`);
|
|
937
|
+
* if (num <= 0) throw new Error(`Must be positive: ${num}`);
|
|
938
|
+
* return num;
|
|
939
|
+
* });
|
|
940
|
+
*
|
|
941
|
+
* const success = parsePositiveNumber('42');
|
|
942
|
+
* console.log(Result.unwrapOkOr(success, 0)); // 42
|
|
943
|
+
*
|
|
944
|
+
* const failure = parsePositiveNumber('abc');
|
|
945
|
+
* console.log(Result.unwrapOkOr(failure, 0)); // 0
|
|
946
|
+
*
|
|
947
|
+
* // Wrapping DOM operations that might fail
|
|
948
|
+
* const getElementText = (id: string): Result<string, Error> =>
|
|
949
|
+
* Result.fromThrowable(() => {
|
|
950
|
+
* const element = document.getElementById(id);
|
|
951
|
+
* if (!element) throw new Error(`Element not found: ${id}`);
|
|
952
|
+
* return element.textContent || "";
|
|
953
|
+
* });
|
|
954
|
+
*
|
|
955
|
+
* // Wrapping file operations
|
|
956
|
+
* const readFileSync = (path: string): Result<string, Error> =>
|
|
957
|
+
* Result.fromThrowable(() =>
|
|
958
|
+
* require('fs').readFileSync(path, 'utf8')
|
|
959
|
+
* );
|
|
960
|
+
* ```
|
|
961
|
+
*/
|
|
962
|
+
export const fromThrowable = <T,>(fn: () => T): Result<T, Error> => {
|
|
963
|
+
try {
|
|
964
|
+
return ok(fn());
|
|
965
|
+
} catch (error) {
|
|
966
|
+
if (error instanceof Error) {
|
|
967
|
+
return err(error);
|
|
968
|
+
}
|
|
969
|
+
const msg = unknownToString(error);
|
|
970
|
+
if (isErr(msg)) {
|
|
971
|
+
return err(new Error(String(error)));
|
|
972
|
+
} else {
|
|
973
|
+
return err(new Error(msg.value));
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
/**
|
|
979
|
+
* Swaps the success and error values of a `Result`.
|
|
980
|
+
* @template R The input `Result.Base` type.
|
|
981
|
+
* @param result The `Result` to swap.
|
|
982
|
+
* @returns A new `Result` with success and error swapped.
|
|
983
|
+
* @example
|
|
984
|
+
* ```typescript
|
|
985
|
+
* const okResult = Result.ok(42);
|
|
986
|
+
* const swapped = Result.swap(okResult);
|
|
987
|
+
* console.log(Result.isErr(swapped)); // true
|
|
988
|
+
* console.log(Result.unwrapErr(swapped)); // 42
|
|
989
|
+
* ```
|
|
990
|
+
*/
|
|
991
|
+
export const swap = <R extends Base>(
|
|
992
|
+
result: R,
|
|
993
|
+
): Result<UnwrapErr<R>, UnwrapOk<R>> =>
|
|
994
|
+
isOk(result) ? err(unwrapOk(result)) : ok(result.value as UnwrapErr<R>);
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Converts a `Result` to an `Optional`.
|
|
998
|
+
*
|
|
999
|
+
* This conversion is useful when you want to discard error information and only
|
|
1000
|
+
* care about whether an operation succeeded. The error information is lost in
|
|
1001
|
+
* this conversion, so use it when error details are not needed.
|
|
1002
|
+
*
|
|
1003
|
+
* If the `Result` is `Ok`, returns `Some` with the value.
|
|
1004
|
+
* If the `Result` is `Err`, returns `None`.
|
|
1005
|
+
*
|
|
1006
|
+
* @template R The input `Result.Base` type.
|
|
1007
|
+
* @param result The `Result` to convert.
|
|
1008
|
+
* @returns An `Optional<UnwrapOk<R>>` containing the success value or representing `None`.
|
|
1009
|
+
* @example
|
|
1010
|
+
* ```typescript
|
|
1011
|
+
* // Basic conversion
|
|
1012
|
+
* const okResult = Result.ok(42);
|
|
1013
|
+
* const optional = Result.toOptional(okResult);
|
|
1014
|
+
* console.log(Optional.isSome(optional)); // true
|
|
1015
|
+
* console.log(Optional.unwrap(optional)); // 42
|
|
1016
|
+
*
|
|
1017
|
+
* const errResult = Result.err("Network error");
|
|
1018
|
+
* const none = Result.toOptional(errResult);
|
|
1019
|
+
* console.log(Optional.isNone(none)); // true
|
|
1020
|
+
*
|
|
1021
|
+
* // Use case: when you only care about success, not error details
|
|
1022
|
+
* const fetchUserName = (id: number): Result<string, ApiError> => {
|
|
1023
|
+
* // ... implementation
|
|
1024
|
+
* };
|
|
1025
|
+
*
|
|
1026
|
+
* const maybeUserName = Result.toOptional(fetchUserName(123));
|
|
1027
|
+
* const displayName = Optional.unwrapOr(maybeUserName, "Unknown User");
|
|
1028
|
+
*
|
|
1029
|
+
* // Converting multiple Results and filtering successes
|
|
1030
|
+
* const userIds = [1, 2, 3, 4];
|
|
1031
|
+
* const userNames = userIds
|
|
1032
|
+
* .map(fetchUserName)
|
|
1033
|
+
* .map(Result.toOptional)
|
|
1034
|
+
* .filter(Optional.isSome)
|
|
1035
|
+
* .map(Optional.unwrap); // string[]
|
|
1036
|
+
*
|
|
1037
|
+
* // Chaining with Optional operations
|
|
1038
|
+
* const processResult = (r: Result<string, Error>) =>
|
|
1039
|
+
* pipe(Result.toOptional(r))
|
|
1040
|
+
* .map(Optional.map(s => s.toUpperCase()))
|
|
1041
|
+
* .map(Optional.filter(s => s.length > 0))
|
|
1042
|
+
* .value;
|
|
1043
|
+
* ```
|
|
1044
|
+
*/
|
|
1045
|
+
export const toOptional = <R extends Base>(
|
|
1046
|
+
result: R,
|
|
1047
|
+
): Optional<UnwrapOk<R>> =>
|
|
1048
|
+
isOk(result) ? Optional.some(unwrapOk(result)) : Optional.none;
|
|
1049
|
+
|
|
1050
|
+
/**
|
|
1051
|
+
* Returns the `Result` if it is `Ok`, otherwise returns the alternative.
|
|
1052
|
+
* @template R The input `Result.Base` type.
|
|
1053
|
+
* @param result The `Result` to check.
|
|
1054
|
+
* @param alternative The alternative `Result` to return if the first is `Err`.
|
|
1055
|
+
* @returns The first `Result` if `Ok`, otherwise the alternative.
|
|
1056
|
+
* @example
|
|
1057
|
+
* ```typescript
|
|
1058
|
+
* // Regular usage
|
|
1059
|
+
* const primary = Result.err("error");
|
|
1060
|
+
* const fallback = Result.ok("default");
|
|
1061
|
+
* const result = Result.orElse(primary, fallback);
|
|
1062
|
+
* console.log(Result.unwrapOk(result)); // "default"
|
|
1063
|
+
*
|
|
1064
|
+
* // Curried usage for pipe composition
|
|
1065
|
+
* const fallbackTo = Result.orElse(Result.ok("fallback"));
|
|
1066
|
+
* const result2 = pipe(Result.err("error")).map(fallbackTo).value;
|
|
1067
|
+
* console.log(Result.unwrapOk(result2)); // "fallback"
|
|
1068
|
+
* ```
|
|
1069
|
+
*/
|
|
1070
|
+
export const orElse: OrElseFnOverload = (<R extends Base, R2 extends Base>(
|
|
1071
|
+
...args: readonly [result: R, alternative: R2] | readonly [alternative: R2]
|
|
1072
|
+
):
|
|
1073
|
+
| (NarrowToOk<R> | R2)
|
|
1074
|
+
| ((
|
|
1075
|
+
result: Result<UnwrapOk<R>, UnwrapErr<R>>,
|
|
1076
|
+
) => Result<UnwrapOk<R>, UnwrapErr<R>> | R2) => {
|
|
1077
|
+
switch (args.length) {
|
|
1078
|
+
case 2: {
|
|
1079
|
+
const [result, alternative] = args;
|
|
1080
|
+
return isOk(result) ? result : alternative;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1083
|
+
case 1: {
|
|
1084
|
+
// Curried version: one argument (alternative) provided
|
|
1085
|
+
const [alternative] = args;
|
|
1086
|
+
return (result: Result<UnwrapOk<R>, UnwrapErr<R>>) =>
|
|
1087
|
+
orElse(result, alternative);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}) as OrElseFnOverload;
|
|
1091
|
+
|
|
1092
|
+
type OrElseFnOverload = {
|
|
1093
|
+
<R extends Base, R2 extends Base>(
|
|
1094
|
+
result: R,
|
|
1095
|
+
alternative: R2,
|
|
1096
|
+
): NarrowToOk<R> | R2;
|
|
1097
|
+
|
|
1098
|
+
// Curried version
|
|
1099
|
+
<S, E, S2, E2>(
|
|
1100
|
+
alternative: Result<S2, E2>,
|
|
1101
|
+
): (result: Result<S, E>) => Result<S, E> | Result<S2, E2>;
|
|
1102
|
+
};
|
|
1103
|
+
|
|
1104
|
+
/**
|
|
1105
|
+
* Combines two `Result` values into a single `Result` containing a tuple.
|
|
1106
|
+
* If either `Result` is `Err`, returns the first `Err` encountered.
|
|
1107
|
+
* @template S1 The success type of the first `Result`.
|
|
1108
|
+
* @template E1 The error type of the first `Result`.
|
|
1109
|
+
* @template S2 The success type of the second `Result`.
|
|
1110
|
+
* @template E2 The error type of the second `Result`.
|
|
1111
|
+
* @param resultA The first `Result`.
|
|
1112
|
+
* @param resultB The second `Result`.
|
|
1113
|
+
* @returns A `Result` containing a tuple of both values, or the first `Err`.
|
|
1114
|
+
* @example
|
|
1115
|
+
* ```typescript
|
|
1116
|
+
* const a = Result.ok(1);
|
|
1117
|
+
* const b = Result.ok("hello");
|
|
1118
|
+
* const zipped = Result.zip(a, b);
|
|
1119
|
+
* console.log(Result.unwrapOk(zipped)); // [1, "hello"]
|
|
1120
|
+
*
|
|
1121
|
+
* const withErr = Result.zip(a, Result.err("error"));
|
|
1122
|
+
* console.log(Result.unwrapErr(withErr)); // "error"
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
1125
|
+
export const zip = <S1, E1, S2, E2>(
|
|
1126
|
+
resultA: Result<S1, E1>,
|
|
1127
|
+
resultB: Result<S2, E2>,
|
|
1128
|
+
): Result<readonly [S1, S2], E1 | E2> =>
|
|
1129
|
+
isOk(resultA)
|
|
1130
|
+
? isOk(resultB)
|
|
1131
|
+
? ok([resultA.value, resultB.value] as const)
|
|
1132
|
+
: resultB
|
|
1133
|
+
: resultA;
|
|
1134
|
+
}
|