rsult 1.4.0 → 2.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/package.json +7 -6
- package/readme.md +161 -5
- package/rust/option.rs +2822 -0
- package/rust/result.rs +2207 -0
- package/src/lib.ts +3 -1
- package/src/option-async.test.ts +410 -0
- package/src/option-async.ts +467 -0
- package/src/option.test.ts +101 -0
- package/src/option.ts +480 -266
- package/src/result-async.test.ts +485 -0
- package/src/result-async.ts +635 -0
- package/src/result.test.ts +36 -0
- package/src/result.ts +418 -340
- package/src/types.test.ts +409 -0
- package/dist/lib.d.ts +0 -2
- package/dist/lib.js +0 -19
- package/dist/lib.js.map +0 -1
- package/dist/option.d.ts +0 -307
- package/dist/option.js +0 -195
- package/dist/option.js.map +0 -1
- package/dist/result.d.ts +0 -410
- package/dist/result.js +0 -231
- package/dist/result.js.map +0 -1
package/src/option.ts
CHANGED
|
@@ -1,273 +1,279 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// Type-Level Utilities
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
/** Unique brand symbols for nominal typing */
|
|
6
|
+
declare const SomeBrand: unique symbol;
|
|
7
|
+
declare const NoneBrand: unique symbol;
|
|
8
|
+
|
|
9
|
+
/** Extract the inner type from an Option */
|
|
10
|
+
export type UnwrapOption<O> = O extends Option<infer T> ? T : never;
|
|
11
|
+
|
|
12
|
+
/** Unwrap nested Option types for flatten() */
|
|
13
|
+
export type FlattenOption<T> = T extends Option<infer U> ? Option<U> : Option<T>;
|
|
14
|
+
|
|
15
|
+
/** Type-level check for nested Options */
|
|
16
|
+
export type IsNestedOption<T> = T extends Option<any> ? true : false;
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Option Type Definition
|
|
20
|
+
// ============================================================================
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A discriminated union representing an optional value.
|
|
24
|
+
*
|
|
25
|
+
* An `Option<T>` is either `Some(T)` containing a value, or `None` representing absence.
|
|
26
|
+
*
|
|
27
|
+
* @typeParam T - The type of the contained value
|
|
28
|
+
*/
|
|
29
|
+
export type Option<T> = OptionSome<T> | OptionNone<T>;
|
|
30
|
+
|
|
31
|
+
// ============================================================================
|
|
32
|
+
// Core Interfaces
|
|
33
|
+
// ============================================================================
|
|
3
34
|
|
|
4
35
|
export interface IOptionCheck<T> {
|
|
5
36
|
/**
|
|
6
|
-
*
|
|
7
|
-
* @returns true if the Option is Some, otherwise false.
|
|
37
|
+
* Returns `true` if the option is `Some`.
|
|
8
38
|
*
|
|
9
|
-
*
|
|
10
|
-
* const myOption = Some(5);
|
|
11
|
-
* if (myOption.is_some()) {
|
|
12
|
-
* console.log("It's Some!");
|
|
13
|
-
* }
|
|
39
|
+
* This is a type guard that narrows the type to `OptionSome<T>`.
|
|
14
40
|
*/
|
|
15
41
|
is_some(): this is OptionSome<T>;
|
|
16
42
|
|
|
17
43
|
/**
|
|
18
|
-
*
|
|
19
|
-
* @param f The condition to apply to the contained value if it's Some.
|
|
20
|
-
* @returns true if the Option is Some and the condition returns true, otherwise false.
|
|
21
|
-
*
|
|
22
|
-
* Usage Example:
|
|
23
|
-
* const myOption = Some(5);
|
|
24
|
-
* const isGreaterThanThree = myOption.is_some_and(x => x > 3); // true
|
|
44
|
+
* Returns `true` if `Some` and the value satisfies the predicate.
|
|
25
45
|
*/
|
|
26
46
|
is_some_and(f: (arg: T) => boolean): boolean;
|
|
27
47
|
|
|
28
48
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
49
|
+
* Returns `true` if the option is `None`.
|
|
50
|
+
*
|
|
51
|
+
* This is a type guard that narrows the type to `OptionNone<T>`.
|
|
52
|
+
*/
|
|
53
|
+
is_none(): this is OptionNone<T>;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Returns `true` if `None` OR if the value satisfies the predicate.
|
|
57
|
+
*
|
|
58
|
+
* This is the logical complement of `is_some_and` with negated predicate.
|
|
31
59
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* Some(2).is_none_or(x => x > 1) // true
|
|
63
|
+
* Some(0).is_none_or(x => x > 1) // false
|
|
64
|
+
* None().is_none_or(x => x > 1) // true
|
|
65
|
+
* ```
|
|
37
66
|
*/
|
|
38
|
-
|
|
67
|
+
is_none_or(f: (arg: T) => boolean): boolean;
|
|
39
68
|
}
|
|
40
69
|
|
|
41
70
|
export interface IOptionExpect<T> {
|
|
42
71
|
/**
|
|
43
|
-
*
|
|
44
|
-
* @param msg The error message to throw if the Option is None.
|
|
45
|
-
* @returns The contained value if the Option is Some.
|
|
46
|
-
* @throws Error with provided message if the Option is None.
|
|
72
|
+
* Returns the contained `Some` value, or throws with the provided message.
|
|
47
73
|
*
|
|
48
|
-
*
|
|
49
|
-
* const myOption = Some(5);
|
|
50
|
-
* const value = myOption.expect("Expected a value!"); // 5
|
|
74
|
+
* @throws Error with `msg` if this is `None`
|
|
51
75
|
*/
|
|
52
|
-
expect(msg: string): T
|
|
76
|
+
expect(msg: string): T;
|
|
53
77
|
}
|
|
54
78
|
|
|
55
79
|
export interface IOptionTransform<T> {
|
|
56
80
|
/**
|
|
57
|
-
*
|
|
58
|
-
* @param fn The mapping function to apply to the contained value.
|
|
59
|
-
* @returns An Option containing the result of applying fn to the original value if it was Some, else None.
|
|
60
|
-
*
|
|
61
|
-
* Usage Example:
|
|
62
|
-
* const myOption = Some(5);
|
|
63
|
-
* const newOption = myOption.map(x => x * 2); // Some(10)
|
|
81
|
+
* Maps an `Option<T>` to `Option<U>` by applying `fn` to the contained value.
|
|
64
82
|
*/
|
|
65
83
|
map<U>(fn: (arg: T) => U): Option<U>;
|
|
66
84
|
|
|
67
85
|
/**
|
|
68
|
-
*
|
|
69
|
-
* @param defaultVal The default value to return if the Option is None.
|
|
70
|
-
* @param fn The function to apply to the contained value if Some.
|
|
71
|
-
* @returns The result of applying fn to the contained value if this Option is Some, else defaultVal.
|
|
72
|
-
*
|
|
73
|
-
* Usage Example:
|
|
74
|
-
* const myOption = None();
|
|
75
|
-
* const value = myOption.map_or(0, x => x * 2); // 0
|
|
86
|
+
* Returns the provided default (if `None`), or applies `fn` to the contained value.
|
|
76
87
|
*/
|
|
77
88
|
map_or<U>(defaultVal: U, fn: (arg: T) => U): U;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Calls `fn` with the contained value for side effects, then returns the original option.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* Some(5)
|
|
96
|
+
* .inspect(x => console.log(`value: ${x}`))
|
|
97
|
+
* .map(x => x * 2)
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
inspect(f: (val: T) => void): this;
|
|
78
101
|
}
|
|
79
102
|
|
|
80
103
|
export interface IOptionCombine<T> {
|
|
81
104
|
/**
|
|
82
|
-
* Returns
|
|
83
|
-
* @param opt The Option to return if this Option is Some.
|
|
84
|
-
* @returns The passed Option if this Option is Some, else None.
|
|
85
|
-
*
|
|
86
|
-
* Usage Example:
|
|
87
|
-
* const opt1 = Some(5);
|
|
88
|
-
* const opt2 = Some(10);
|
|
89
|
-
* const result = opt1.and(opt2); // Some(10)
|
|
105
|
+
* Returns `None` if `self` is `None`, otherwise returns `opt`.
|
|
90
106
|
*/
|
|
91
107
|
and<U>(opt: Option<U>): Option<U>;
|
|
92
108
|
|
|
93
109
|
/**
|
|
94
|
-
* Returns
|
|
95
|
-
*
|
|
96
|
-
* @returns An Option containing the result of applying fn to the original value if it was Some, else None.
|
|
97
|
-
*
|
|
98
|
-
* Usage Example:
|
|
99
|
-
* const myOption = Some(5);
|
|
100
|
-
* const newOption = myOption.and_then(x => Some(x * 2)); // Some(10)
|
|
110
|
+
* Returns `None` if `self` is `None`, otherwise calls `fn` with the wrapped value.
|
|
111
|
+
* This is the monadic bind operation (flatMap).
|
|
101
112
|
*/
|
|
102
113
|
and_then<U>(fn: (arg: T) => Option<U>): Option<U>;
|
|
103
114
|
|
|
104
115
|
/**
|
|
105
|
-
* Returns
|
|
106
|
-
* @param opt The alternative Option to return if this Option is None.
|
|
107
|
-
* @returns This Option if it is Some, otherwise the passed Option.
|
|
108
|
-
*
|
|
109
|
-
* Usage Example:
|
|
110
|
-
* const opt1 = None();
|
|
111
|
-
* const opt2 = Some(10);
|
|
112
|
-
* const result = opt1.or(opt2); // Some(10)
|
|
116
|
+
* Returns `self` if `Some`, otherwise returns `opt`.
|
|
113
117
|
*/
|
|
114
|
-
or<U>(opt: Option<U>): Option<T
|
|
118
|
+
or<U>(opt: Option<U>): Option<T | U>;
|
|
115
119
|
|
|
116
120
|
/**
|
|
117
|
-
* Returns
|
|
118
|
-
* @param fn The function that produces an Option to return if this Option is None.
|
|
119
|
-
* @returns This Option if it is Some, otherwise the Option produced by fn.
|
|
120
|
-
*
|
|
121
|
-
* Usage Example:
|
|
122
|
-
* const opt1 = None();
|
|
123
|
-
* const result = opt1.or_else(() => Some(10)); // Some(10)
|
|
121
|
+
* Returns `self` if `Some`, otherwise calls `fn` and returns the result.
|
|
124
122
|
*/
|
|
125
|
-
or_else<U>(fn: () => Option<U>): Option<T
|
|
123
|
+
or_else<U>(fn: () => Option<U>): Option<T | U>;
|
|
126
124
|
|
|
127
125
|
/**
|
|
128
|
-
* Returns
|
|
129
|
-
* @param optb The other Option to compare with.
|
|
130
|
-
* @returns None if both Options are Some, otherwise the Option that is Some.
|
|
131
|
-
*
|
|
132
|
-
* Usage Example:
|
|
133
|
-
* const opt1 = Some(5);
|
|
134
|
-
* const opt2 = None();
|
|
135
|
-
* const result = opt1.xor(opt2); // Some(5)
|
|
126
|
+
* Returns `Some` if exactly one of `self` and `optb` is `Some`, otherwise `None`.
|
|
136
127
|
*/
|
|
137
128
|
xor(optb: Option<T>): Option<T>;
|
|
138
129
|
}
|
|
139
130
|
|
|
140
131
|
export interface IOptionUtility<T> {
|
|
141
132
|
/**
|
|
142
|
-
*
|
|
143
|
-
* @returns The contained value if this Option is Some.
|
|
144
|
-
* @throws Error if this Option is None.
|
|
133
|
+
* Returns the contained `Some` value, or throws.
|
|
145
134
|
*
|
|
146
|
-
*
|
|
147
|
-
* const myOption = Some(5);
|
|
148
|
-
* const value = myOption.unwrap(); // 5
|
|
135
|
+
* @throws Error if this is `None`
|
|
149
136
|
*/
|
|
150
|
-
unwrap(): T
|
|
137
|
+
unwrap(): T;
|
|
151
138
|
|
|
152
139
|
/**
|
|
153
|
-
* Returns the contained value
|
|
154
|
-
* @param optb The alternative value to return if this Option is None.
|
|
155
|
-
* @returns The contained value if this Option is Some, else optb.
|
|
156
|
-
*
|
|
157
|
-
* Usage Example:
|
|
158
|
-
* const myOption = None();
|
|
159
|
-
* const value = myOption.unwrap_or(10); // 10
|
|
140
|
+
* Returns the contained `Some` value, or the provided default.
|
|
160
141
|
*/
|
|
161
142
|
unwrap_or(optb: T): T;
|
|
162
143
|
|
|
163
144
|
/**
|
|
164
|
-
* Returns the contained value
|
|
165
|
-
* @param fn The function to compute the alternative value from if this Option is None.
|
|
166
|
-
* @returns The contained value if this Option is Some, else the result of fn.
|
|
167
|
-
*
|
|
168
|
-
* Usage Example:
|
|
169
|
-
* const myOption = None();
|
|
170
|
-
* const value = myOption.unwrap_or_else(() => 10); // 10
|
|
145
|
+
* Returns the contained `Some` value, or computes it from `fn`.
|
|
171
146
|
*/
|
|
172
147
|
unwrap_or_else(fn: () => T): T;
|
|
173
148
|
|
|
174
149
|
/**
|
|
175
|
-
* Returns the contained value
|
|
176
|
-
* @returns The contained value if Some, else the type’s default value.
|
|
177
|
-
*
|
|
178
|
-
* Usage Example:
|
|
179
|
-
* const myOption is None<number>();
|
|
180
|
-
* const value = myOption.unwrap_or_default(); // 0
|
|
150
|
+
* Returns the contained `Some` value, or `null` if `None`.
|
|
181
151
|
*/
|
|
182
152
|
unwrap_or_default(): T | null;
|
|
183
153
|
}
|
|
184
154
|
|
|
185
155
|
export interface IOptionMutate<T> {
|
|
186
156
|
/**
|
|
187
|
-
* Takes the
|
|
188
|
-
* @returns The Option containing the original value before it was taken.
|
|
157
|
+
* Takes the value out of the option, leaving a `None` in its place.
|
|
189
158
|
*
|
|
190
|
-
*
|
|
191
|
-
* const myOption = Some(5);
|
|
192
|
-
* const takenValue = myOption.take(); // Some(5), myOption is now None
|
|
159
|
+
* @returns An `Option` containing the original value
|
|
193
160
|
*/
|
|
194
161
|
take(): Option<T>;
|
|
195
162
|
|
|
196
163
|
/**
|
|
197
|
-
* Takes the
|
|
198
|
-
* @param predicate The predicate to apply to the contained value.
|
|
199
|
-
* @returns The Option containing the original value if the predicate returns true, otherwise None.
|
|
200
|
-
*
|
|
201
|
-
* Usage Example:
|
|
202
|
-
* const myOption = Some(5);
|
|
203
|
-
* const takenValue = myOption.take_if(x => x > 3); // Some(5), myOption is now None
|
|
164
|
+
* Takes the value out if it satisfies the predicate, leaving `None` in its place.
|
|
204
165
|
*/
|
|
205
166
|
take_if(predicate: (arg: T) => boolean): Option<T>;
|
|
206
167
|
|
|
207
168
|
/**
|
|
208
|
-
* Replaces the
|
|
209
|
-
*
|
|
210
|
-
* @returns An Option containing the old value
|
|
211
|
-
|
|
212
|
-
* Usage Example:
|
|
213
|
-
* const myOption = Some(5);
|
|
214
|
-
* const oldValue = myOption.replace(10); // Some(5), myOption now contains 10
|
|
215
|
-
*
|
|
216
|
-
*/
|
|
169
|
+
* Replaces the actual value with the provided one, returning the old value.
|
|
170
|
+
*
|
|
171
|
+
* @returns An `Option` containing the old value
|
|
172
|
+
*/
|
|
217
173
|
replace(value: T): Option<T>;
|
|
218
174
|
}
|
|
219
175
|
|
|
220
176
|
export interface IOptionZip<T> {
|
|
221
177
|
/**
|
|
222
|
-
*
|
|
223
|
-
* @param other The other Option to zip with.
|
|
224
|
-
* @returns An Option containing a tuple of the two Option values if both are Some, otherwise None.
|
|
178
|
+
* Zips `self` with another `Option`.
|
|
225
179
|
*
|
|
226
|
-
*
|
|
227
|
-
* const opt1 = Some(5);
|
|
228
|
-
* const opt2 = Some("hello");
|
|
229
|
-
* const zipped = opt1.zip(opt2); // Some([5, "hello"])
|
|
180
|
+
* Returns `Some([T, U])` if both are `Some`, otherwise `None`.
|
|
230
181
|
*/
|
|
231
182
|
zip<U>(other: Option<U>): Option<[T, U]>;
|
|
232
183
|
|
|
233
184
|
/**
|
|
234
|
-
*
|
|
235
|
-
* @param other The other Option to zip with.
|
|
236
|
-
* @param f The function to apply to the values if both Options are Some.
|
|
237
|
-
* @returns An Option containing the result of the function if both Options are Some, otherwise None.
|
|
185
|
+
* Zips `self` and another `Option` with a function.
|
|
238
186
|
*
|
|
239
|
-
*
|
|
240
|
-
* const opt1 = Some(5);
|
|
241
|
-
* const opt2 = Some(10);
|
|
242
|
-
* const added = opt1.zip_with(opt2, (a, b) => a + b); // Some(15)
|
|
187
|
+
* Returns `Some(f(t, u))` if both are `Some`, otherwise `None`.
|
|
243
188
|
*/
|
|
244
189
|
zip_with<U, R>(other: Option<U>, f: (val: T, other: U) => R): Option<R>;
|
|
245
190
|
}
|
|
191
|
+
|
|
246
192
|
export interface IOptionFilter<T> {
|
|
247
193
|
/**
|
|
248
|
-
*
|
|
249
|
-
* @param predicate The predicate function to apply.
|
|
250
|
-
* @returns The original Option if it is Some and the predicate holds, else None.
|
|
194
|
+
* Returns `None` if `self` is `None`, otherwise calls `predicate` with the wrapped value.
|
|
251
195
|
*
|
|
252
|
-
*
|
|
253
|
-
* const myOption = Some(5);
|
|
254
|
-
* const filteredOption = myOption.filter(x => x > 3); // Some(5)
|
|
196
|
+
* Returns `Some(t)` if the predicate returns `true`, otherwise `None`.
|
|
255
197
|
*/
|
|
256
198
|
filter(predicate: (arg: T) => boolean): Option<T>;
|
|
257
199
|
}
|
|
258
200
|
|
|
259
201
|
export interface IOptionFlatten<T> {
|
|
260
202
|
/**
|
|
261
|
-
* Flattens
|
|
262
|
-
*
|
|
203
|
+
* Flattens an `Option<Option<U>>` to `Option<U>`.
|
|
204
|
+
*
|
|
205
|
+
* If `T` is not an `Option`, this returns the option unchanged.
|
|
206
|
+
*/
|
|
207
|
+
flatten(): FlattenOption<T>;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Forward declaration for Result types (will be imported at runtime)
|
|
211
|
+
type ResultLike<T, E> = { is_ok(): boolean; value: T | E; _tag: 'Ok' | 'Err' };
|
|
212
|
+
|
|
213
|
+
export interface IOptionConvert<T> {
|
|
214
|
+
/**
|
|
215
|
+
* Transforms `Option<T>` into `Result<T, E>`.
|
|
216
|
+
*
|
|
217
|
+
* Returns `Ok(v)` if `Some(v)`, otherwise `Err(err)`.
|
|
218
|
+
*
|
|
219
|
+
* @example
|
|
220
|
+
* ```typescript
|
|
221
|
+
* Some(5).ok_or("missing") // Ok(5)
|
|
222
|
+
* None().ok_or("missing") // Err("missing")
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
ok_or<E>(err: E): { _tag: 'Ok' | 'Err'; value: T | E };
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Transforms `Option<T>` into `Result<T, E>` with a lazily computed error.
|
|
229
|
+
*
|
|
230
|
+
* Returns `Ok(v)` if `Some(v)`, otherwise `Err(err())`.
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```typescript
|
|
234
|
+
* Some(5).ok_or_else(() => new Error("missing")) // Ok(5)
|
|
235
|
+
* None().ok_or_else(() => new Error("missing")) // Err(Error)
|
|
236
|
+
* ```
|
|
237
|
+
*/
|
|
238
|
+
ok_or_else<E>(err: () => E): { _tag: 'Ok' | 'Err'; value: T | E };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export interface IOptionAdvanced<T> {
|
|
242
|
+
/**
|
|
243
|
+
* Unzips an option containing a tuple into a tuple of options.
|
|
263
244
|
*
|
|
264
|
-
*
|
|
265
|
-
*
|
|
266
|
-
*
|
|
245
|
+
* @example
|
|
246
|
+
* ```typescript
|
|
247
|
+
* Some([1, "hi"]).unzip() // [Some(1), Some("hi")]
|
|
248
|
+
* None().unzip() // [None, None]
|
|
249
|
+
* ```
|
|
267
250
|
*/
|
|
268
|
-
|
|
251
|
+
unzip(): T extends [infer A, infer B] ? [Option<A>, Option<B>] : never;
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Transposes an `Option` of a `Result` into a `Result` of an `Option`.
|
|
255
|
+
*
|
|
256
|
+
* - `None` → `Ok(None)`
|
|
257
|
+
* - `Some(Ok(x))` → `Ok(Some(x))`
|
|
258
|
+
* - `Some(Err(e))` → `Err(e)`
|
|
259
|
+
*
|
|
260
|
+
* @example
|
|
261
|
+
* ```typescript
|
|
262
|
+
* Some(Ok(5)).transpose() // Ok(Some(5))
|
|
263
|
+
* Some(Err("e")).transpose() // Err("e")
|
|
264
|
+
* None().transpose() // Ok(None)
|
|
265
|
+
* ```
|
|
266
|
+
*/
|
|
267
|
+
transpose(): T extends { _tag: 'Ok'; value: infer U }
|
|
268
|
+
? { _tag: 'Ok'; value: Option<U> }
|
|
269
|
+
: T extends { _tag: 'Err'; value: infer E }
|
|
270
|
+
? { _tag: 'Err'; value: E }
|
|
271
|
+
: { _tag: 'Ok'; value: Option<unknown> };
|
|
269
272
|
}
|
|
270
273
|
|
|
274
|
+
/**
|
|
275
|
+
* The complete Option interface combining all capability interfaces.
|
|
276
|
+
*/
|
|
271
277
|
export interface IOption<T> extends
|
|
272
278
|
IOptionCheck<T>,
|
|
273
279
|
IOptionExpect<T>,
|
|
@@ -277,15 +283,31 @@ export interface IOption<T> extends
|
|
|
277
283
|
IOptionMutate<T>,
|
|
278
284
|
IOptionZip<T>,
|
|
279
285
|
IOptionFilter<T>,
|
|
280
|
-
IOptionFlatten<T
|
|
281
|
-
|
|
282
|
-
|
|
286
|
+
IOptionFlatten<T>,
|
|
287
|
+
IOptionConvert<T>,
|
|
288
|
+
IOptionAdvanced<T> {}
|
|
289
|
+
|
|
290
|
+
// ============================================================================
|
|
291
|
+
// OptionSome Implementation
|
|
292
|
+
// ============================================================================
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The `Some` variant of `Option<T>`, representing a present value.
|
|
296
|
+
*
|
|
297
|
+
* Uses branded typing for nominal type safety.
|
|
298
|
+
*
|
|
299
|
+
* @typeParam T - The type of the contained value
|
|
300
|
+
*/
|
|
301
|
+
export class OptionSome<out T> implements IOption<T> {
|
|
302
|
+
/** Brand for nominal typing - ensures OptionSome is distinct from OptionNone */
|
|
303
|
+
declare readonly [SomeBrand]: T;
|
|
304
|
+
|
|
305
|
+
/** Discriminant tag for runtime type checking */
|
|
283
306
|
readonly _tag = 'Some' as const;
|
|
284
|
-
// @ts-ignore
|
|
285
|
-
readonly _T: T;
|
|
286
307
|
|
|
287
|
-
constructor(
|
|
308
|
+
constructor(public value: T) {}
|
|
288
309
|
|
|
310
|
+
// Type guards
|
|
289
311
|
is_some(): this is OptionSome<T> {
|
|
290
312
|
return true;
|
|
291
313
|
}
|
|
@@ -294,22 +316,50 @@ export class OptionSome<T> implements IOption<T> {
|
|
|
294
316
|
return f(this.value);
|
|
295
317
|
}
|
|
296
318
|
|
|
297
|
-
is_none(): this is
|
|
319
|
+
is_none(): this is OptionNone<T> {
|
|
298
320
|
return false;
|
|
299
321
|
}
|
|
300
322
|
|
|
323
|
+
is_none_or(f: (arg: T) => boolean): boolean {
|
|
324
|
+
return f(this.value);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Extraction
|
|
301
328
|
expect(_msg: string): T {
|
|
302
329
|
return this.value;
|
|
303
330
|
}
|
|
304
331
|
|
|
305
|
-
|
|
306
|
-
return
|
|
332
|
+
unwrap(): T {
|
|
333
|
+
return this.value;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
unwrap_or(_optb: T): T {
|
|
337
|
+
return this.value;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
unwrap_or_else(_fn: () => T): T {
|
|
341
|
+
return this.value;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
unwrap_or_default(): T {
|
|
345
|
+
return this.value;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Transformations
|
|
349
|
+
map<U>(fn: (arg: T) => U): OptionSome<U> {
|
|
350
|
+
return new OptionSome(fn(this.value));
|
|
307
351
|
}
|
|
308
352
|
|
|
309
353
|
map_or<U>(_defaultVal: U, fn: (arg: T) => U): U {
|
|
310
354
|
return fn(this.value);
|
|
311
355
|
}
|
|
312
356
|
|
|
357
|
+
inspect(f: (val: T) => void): this {
|
|
358
|
+
f(this.value);
|
|
359
|
+
return this;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Combinators
|
|
313
363
|
and<U>(opt: Option<U>): Option<U> {
|
|
314
364
|
return opt;
|
|
315
365
|
}
|
|
@@ -318,106 +368,134 @@ export class OptionSome<T> implements IOption<T> {
|
|
|
318
368
|
return fn(this.value);
|
|
319
369
|
}
|
|
320
370
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
return
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
or<U>(_opt: Option<U>): Option<T> {
|
|
330
|
-
return this as any;
|
|
371
|
+
/**
|
|
372
|
+
* On Some, `or` returns self.
|
|
373
|
+
* Returns this instance typed as Option<T | U> for type compatibility.
|
|
374
|
+
*/
|
|
375
|
+
or<U>(_opt: Option<U>): OptionSome<T> {
|
|
376
|
+
return this;
|
|
331
377
|
}
|
|
332
378
|
|
|
333
|
-
|
|
334
|
-
|
|
379
|
+
/**
|
|
380
|
+
* On Some, `or_else` returns self.
|
|
381
|
+
*/
|
|
382
|
+
or_else<U>(_fn: () => Option<U>): OptionSome<T> {
|
|
383
|
+
return this;
|
|
335
384
|
}
|
|
336
385
|
|
|
337
386
|
xor(optb: Option<T>): Option<T> {
|
|
338
387
|
if (optb.is_some()) {
|
|
339
|
-
return
|
|
388
|
+
return new OptionNone();
|
|
340
389
|
}
|
|
341
|
-
|
|
342
390
|
return this;
|
|
343
391
|
}
|
|
344
392
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
unwrap_or_else(_fn: () => T): T {
|
|
354
|
-
return this.value;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
unwrap_or_default(): T | null {
|
|
358
|
-
return this.value;
|
|
393
|
+
// Filtering
|
|
394
|
+
filter(predicate: (arg: T) => boolean): Option<T> {
|
|
395
|
+
if (predicate(this.value)) {
|
|
396
|
+
return this;
|
|
397
|
+
}
|
|
398
|
+
return new OptionNone();
|
|
359
399
|
}
|
|
360
400
|
|
|
401
|
+
// Mutation
|
|
361
402
|
take(): Option<T> {
|
|
362
403
|
const value = this.value;
|
|
363
|
-
//
|
|
364
|
-
this.value = undefined as any;
|
|
365
|
-
return new OptionSome
|
|
404
|
+
// Mutate in place - this is intentional per the Rust API
|
|
405
|
+
(this as { value: T | undefined }).value = undefined as any;
|
|
406
|
+
return new OptionSome(value);
|
|
366
407
|
}
|
|
367
408
|
|
|
368
409
|
take_if(predicate: (arg: T) => boolean): Option<T> {
|
|
369
410
|
if (predicate(this.value)) {
|
|
370
411
|
return this.take();
|
|
371
412
|
}
|
|
372
|
-
|
|
373
|
-
return None<T>();
|
|
413
|
+
return new OptionNone();
|
|
374
414
|
}
|
|
375
415
|
|
|
376
416
|
replace(value: T): Option<T> {
|
|
377
417
|
const oldValue = this.value;
|
|
378
|
-
// @ts-ignore "administrative override" :-)
|
|
379
418
|
this.value = value;
|
|
380
|
-
return new OptionSome
|
|
419
|
+
return new OptionSome(oldValue);
|
|
381
420
|
}
|
|
382
421
|
|
|
422
|
+
// Zipping
|
|
383
423
|
zip<U>(other: Option<U>): Option<[T, U]> {
|
|
384
424
|
if (other.is_some()) {
|
|
385
|
-
return new OptionSome<[T, U]>([this.value, other.
|
|
425
|
+
return new OptionSome<[T, U]>([this.value, other.value]);
|
|
386
426
|
}
|
|
387
|
-
|
|
388
|
-
return new OptionNone<[T, U]>();
|
|
427
|
+
return new OptionNone();
|
|
389
428
|
}
|
|
390
429
|
|
|
391
430
|
zip_with<U, R>(other: Option<U>, f: (val: T, other: U) => R): Option<R> {
|
|
392
431
|
if (other.is_some()) {
|
|
393
|
-
return new OptionSome
|
|
432
|
+
return new OptionSome(f(this.value, other.value));
|
|
433
|
+
}
|
|
434
|
+
return new OptionNone();
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Flattening
|
|
438
|
+
flatten(): FlattenOption<T> {
|
|
439
|
+
const val = this.value;
|
|
440
|
+
if (val instanceof OptionSome) {
|
|
441
|
+
return val as FlattenOption<T>;
|
|
442
|
+
}
|
|
443
|
+
if (val instanceof OptionNone) {
|
|
444
|
+
return val as FlattenOption<T>;
|
|
394
445
|
}
|
|
446
|
+
// T is not an Option, return self unchanged
|
|
447
|
+
return this as unknown as FlattenOption<T>;
|
|
448
|
+
}
|
|
395
449
|
|
|
396
|
-
|
|
450
|
+
// Conversion to Result
|
|
451
|
+
ok_or<E>(_err: E): { _tag: 'Ok'; value: T } {
|
|
452
|
+
return { _tag: 'Ok', value: this.value };
|
|
397
453
|
}
|
|
398
454
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
455
|
+
ok_or_else<E>(_err: () => E): { _tag: 'Ok'; value: T } {
|
|
456
|
+
return { _tag: 'Ok', value: this.value };
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Advanced
|
|
460
|
+
unzip(): T extends [infer A, infer B] ? [Option<A>, Option<B>] : never {
|
|
461
|
+
const [a, b] = this.value as [unknown, unknown];
|
|
462
|
+
return [new OptionSome(a), new OptionSome(b)] as any;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
transpose(): any {
|
|
466
|
+
const result = this.value as { _tag: 'Ok' | 'Err'; value: unknown; is_ok?(): boolean };
|
|
467
|
+
if (result._tag === 'Ok' || (result.is_ok && result.is_ok())) {
|
|
468
|
+
return { _tag: 'Ok', value: new OptionSome(result.value) };
|
|
403
469
|
} else {
|
|
404
|
-
|
|
405
|
-
// we return None<T>(). This assumes that None<T>() creates an OptionNone<T> instance.
|
|
406
|
-
return None<T>();
|
|
470
|
+
return { _tag: 'Err', value: result.value };
|
|
407
471
|
}
|
|
408
472
|
}
|
|
409
473
|
}
|
|
410
474
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
475
|
+
// ============================================================================
|
|
476
|
+
// OptionNone Implementation
|
|
477
|
+
// ============================================================================
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* The `None` variant of `Option<T>`, representing absence of a value.
|
|
481
|
+
*
|
|
482
|
+
* Uses branded typing for nominal type safety.
|
|
483
|
+
*
|
|
484
|
+
* @typeParam T - The phantom type parameter for type compatibility
|
|
485
|
+
*/
|
|
486
|
+
export class OptionNone<out T = never> implements IOption<T> {
|
|
487
|
+
/** Brand for nominal typing - ensures OptionNone is distinct from OptionSome */
|
|
488
|
+
declare readonly [NoneBrand]: void;
|
|
489
|
+
|
|
490
|
+
/** Discriminant tag for runtime type checking */
|
|
491
|
+
readonly _tag = 'None' as const;
|
|
492
|
+
|
|
493
|
+
// Type guards
|
|
494
|
+
is_some(): this is OptionSome<T> {
|
|
417
495
|
return false;
|
|
418
496
|
}
|
|
419
497
|
|
|
420
|
-
is_some_and(_f: (arg: T) => boolean):
|
|
498
|
+
is_some_and(_f: (arg: T) => boolean): false {
|
|
421
499
|
return false;
|
|
422
500
|
}
|
|
423
501
|
|
|
@@ -425,28 +503,60 @@ export class OptionNone<T> implements IOption<T> {
|
|
|
425
503
|
return true;
|
|
426
504
|
}
|
|
427
505
|
|
|
506
|
+
is_none_or(_f: (arg: T) => boolean): true {
|
|
507
|
+
return true;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// Extraction
|
|
428
511
|
expect(msg: string): never {
|
|
429
512
|
throw new Error(msg);
|
|
430
513
|
}
|
|
431
514
|
|
|
432
|
-
|
|
433
|
-
|
|
515
|
+
unwrap(): never {
|
|
516
|
+
throw new Error('Called Option.unwrap() on a None value');
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
unwrap_or(optb: T): T {
|
|
520
|
+
return optb;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
unwrap_or_else(fn: () => T): T {
|
|
524
|
+
return fn();
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
unwrap_or_default(): null {
|
|
528
|
+
return null;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Transformations
|
|
532
|
+
/**
|
|
533
|
+
* On None, map returns None with updated type parameter.
|
|
534
|
+
*/
|
|
535
|
+
map<U>(_fn: (arg: T) => U): OptionNone<U> {
|
|
536
|
+
return new OptionNone<U>();
|
|
434
537
|
}
|
|
435
538
|
|
|
436
539
|
map_or<U>(defaultVal: U, _fn: (arg: T) => U): U {
|
|
437
540
|
return defaultVal;
|
|
438
541
|
}
|
|
439
542
|
|
|
440
|
-
|
|
441
|
-
return this
|
|
543
|
+
inspect(_f: (val: T) => void): this {
|
|
544
|
+
return this;
|
|
442
545
|
}
|
|
443
546
|
|
|
444
|
-
|
|
445
|
-
|
|
547
|
+
// Combinators
|
|
548
|
+
/**
|
|
549
|
+
* On None, `and` returns None with updated type parameter.
|
|
550
|
+
*/
|
|
551
|
+
and<U>(_opt: Option<U>): OptionNone<U> {
|
|
552
|
+
return new OptionNone<U>();
|
|
446
553
|
}
|
|
447
554
|
|
|
448
|
-
|
|
449
|
-
|
|
555
|
+
/**
|
|
556
|
+
* On None, `and_then` returns None with updated type parameter.
|
|
557
|
+
*/
|
|
558
|
+
and_then<U>(_fn: (arg: T) => Option<U>): OptionNone<U> {
|
|
559
|
+
return new OptionNone<U>();
|
|
450
560
|
}
|
|
451
561
|
|
|
452
562
|
or<U>(opt: Option<U>): Option<U> {
|
|
@@ -461,64 +571,168 @@ export class OptionNone<T> implements IOption<T> {
|
|
|
461
571
|
return optb;
|
|
462
572
|
}
|
|
463
573
|
|
|
464
|
-
|
|
465
|
-
|
|
574
|
+
// Filtering
|
|
575
|
+
/**
|
|
576
|
+
* On None, filter returns None.
|
|
577
|
+
*/
|
|
578
|
+
filter(_predicate: (arg: T) => boolean): OptionNone<T> {
|
|
579
|
+
return this;
|
|
466
580
|
}
|
|
467
581
|
|
|
468
|
-
|
|
469
|
-
|
|
582
|
+
// Mutation
|
|
583
|
+
take(): OptionNone<T> {
|
|
584
|
+
return this;
|
|
470
585
|
}
|
|
471
586
|
|
|
472
|
-
|
|
473
|
-
return
|
|
587
|
+
take_if(_predicate: (arg: T) => boolean): OptionNone<T> {
|
|
588
|
+
return this;
|
|
474
589
|
}
|
|
475
590
|
|
|
476
|
-
|
|
477
|
-
|
|
591
|
+
/**
|
|
592
|
+
* On None, replace returns Some with the provided value.
|
|
593
|
+
*
|
|
594
|
+
* Note: This differs from Rust's semantics where replace would mutate
|
|
595
|
+
* self to become Some and return the old value (None). Here we maintain
|
|
596
|
+
* immutability - self remains None and we return Some(value).
|
|
597
|
+
*/
|
|
598
|
+
replace(value: T): OptionSome<T> {
|
|
599
|
+
return new OptionSome<T>(value);
|
|
478
600
|
}
|
|
479
601
|
|
|
480
|
-
|
|
481
|
-
|
|
602
|
+
// Zipping
|
|
603
|
+
zip<U>(_other: Option<U>): OptionNone<[T, U]> {
|
|
604
|
+
return new OptionNone();
|
|
482
605
|
}
|
|
483
606
|
|
|
484
|
-
|
|
485
|
-
return
|
|
607
|
+
zip_with<U, R>(_other: Option<U>, _f: (val: T, other: U) => R): OptionNone<R> {
|
|
608
|
+
return new OptionNone();
|
|
486
609
|
}
|
|
487
610
|
|
|
488
|
-
|
|
489
|
-
|
|
611
|
+
// Flattening
|
|
612
|
+
flatten(): FlattenOption<T> {
|
|
613
|
+
return new OptionNone() as FlattenOption<T>;
|
|
490
614
|
}
|
|
491
615
|
|
|
492
|
-
|
|
493
|
-
|
|
616
|
+
// Conversion to Result
|
|
617
|
+
ok_or<E>(err: E): { _tag: 'Err'; value: E } {
|
|
618
|
+
return { _tag: 'Err', value: err };
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
ok_or_else<E>(err: () => E): { _tag: 'Err'; value: E } {
|
|
622
|
+
return { _tag: 'Err', value: err() };
|
|
494
623
|
}
|
|
495
624
|
|
|
496
|
-
|
|
497
|
-
|
|
625
|
+
// Advanced
|
|
626
|
+
unzip(): T extends [infer A, infer B] ? [Option<A>, Option<B>] : never {
|
|
627
|
+
return [new OptionNone(), new OptionNone()] as any;
|
|
498
628
|
}
|
|
499
629
|
|
|
500
|
-
|
|
501
|
-
return
|
|
630
|
+
transpose(): any {
|
|
631
|
+
return { _tag: 'Ok', value: new OptionNone() };
|
|
502
632
|
}
|
|
503
633
|
}
|
|
504
634
|
|
|
505
|
-
|
|
506
|
-
|
|
635
|
+
// ============================================================================
|
|
636
|
+
// Factory Functions
|
|
637
|
+
// ============================================================================
|
|
638
|
+
|
|
639
|
+
/**
|
|
640
|
+
* Creates a `Some` option containing the given value.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* const opt = Some(42); // Option<number>
|
|
644
|
+
* const mapped = opt.map(x => x * 2); // Option<number>
|
|
645
|
+
*/
|
|
646
|
+
export const Some = <T>(val: T): OptionSome<T> => {
|
|
647
|
+
return new OptionSome(val);
|
|
507
648
|
};
|
|
508
649
|
|
|
509
|
-
|
|
650
|
+
/**
|
|
651
|
+
* Creates a `None` option of the specified type.
|
|
652
|
+
*
|
|
653
|
+
* @example
|
|
654
|
+
* const opt = None<number>(); // Option<number>
|
|
655
|
+
* const fallback = opt.or(Some(0)); // Option<number>
|
|
656
|
+
*/
|
|
657
|
+
export const None = <T = never>(): OptionNone<T> => {
|
|
510
658
|
return new OptionNone<T>();
|
|
511
659
|
};
|
|
512
660
|
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
661
|
+
// ============================================================================
|
|
662
|
+
// Utility Functions
|
|
663
|
+
// ============================================================================
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Converts a nullable value to an Option.
|
|
667
|
+
*
|
|
668
|
+
* Returns `Some(val)` if the value is not null/undefined, otherwise `None`.
|
|
669
|
+
*
|
|
670
|
+
* @example
|
|
671
|
+
* const opt1 = option_from_nullable("hello"); // Some("hello")
|
|
672
|
+
* const opt2 = option_from_nullable(null); // None
|
|
673
|
+
* const opt3 = option_from_nullable(undefined); // None
|
|
674
|
+
*/
|
|
675
|
+
export const option_from_nullable = <T>(val: T | null | undefined): Option<NonNullable<T>> => {
|
|
676
|
+
if (val === null || val === undefined) {
|
|
677
|
+
return None();
|
|
678
|
+
}
|
|
679
|
+
return Some(val as NonNullable<T>);
|
|
680
|
+
};
|
|
518
681
|
|
|
519
|
-
|
|
520
|
-
|
|
682
|
+
/**
|
|
683
|
+
* Converts a Promise to an Option wrapped in a Promise.
|
|
684
|
+
*
|
|
685
|
+
* If the promise resolves, returns `Some(value)`.
|
|
686
|
+
* If it rejects, returns `None`.
|
|
687
|
+
*
|
|
688
|
+
* @example
|
|
689
|
+
* const opt = await option_from_promise(fetch('/api/data'));
|
|
690
|
+
* // Option<Response>
|
|
691
|
+
*/
|
|
692
|
+
export const option_from_promise = <T>(promise: Promise<T>): Promise<Option<T>> =>
|
|
693
|
+
promise.then(Some).catch(() => None<T>());
|
|
694
|
+
|
|
695
|
+
// ============================================================================
|
|
696
|
+
// Advanced Type Utilities
|
|
697
|
+
// ============================================================================
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Type-safe match expression for Option.
|
|
701
|
+
*
|
|
702
|
+
* @example
|
|
703
|
+
* const message = matchOption(option, {
|
|
704
|
+
* Some: (value) => `Found: ${value}`,
|
|
705
|
+
* None: () => "Not found",
|
|
706
|
+
* });
|
|
707
|
+
*/
|
|
708
|
+
export function matchOption<T, R>(
|
|
709
|
+
option: Option<T>,
|
|
710
|
+
handlers: {
|
|
711
|
+
Some: (value: T) => R;
|
|
712
|
+
None: () => R;
|
|
713
|
+
}
|
|
714
|
+
): R {
|
|
715
|
+
if (option.is_some()) {
|
|
716
|
+
return handlers.Some(option.value);
|
|
717
|
+
} else {
|
|
718
|
+
return handlers.None();
|
|
719
|
+
}
|
|
720
|
+
}
|
|
521
721
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
722
|
+
/**
|
|
723
|
+
* Transposes an Option of a Result into a Result of an Option.
|
|
724
|
+
*
|
|
725
|
+
* - `None` -> `Ok(None)`
|
|
726
|
+
* - `Some(Ok(x))` -> `Ok(Some(x))`
|
|
727
|
+
* - `Some(Err(e))` -> `Err(e)`
|
|
728
|
+
*
|
|
729
|
+
* This is useful for error handling in option chains.
|
|
730
|
+
*/
|
|
731
|
+
// Note: This requires importing Result types, so we use a type-only approach
|
|
732
|
+
export type TransposeOption<T> = T extends { is_ok(): boolean; value: infer V }
|
|
733
|
+
? T extends { _tag: 'Ok' }
|
|
734
|
+
? { _tag: 'Ok'; value: Option<V> }
|
|
735
|
+
: T extends { _tag: 'Err' }
|
|
736
|
+
? T
|
|
737
|
+
: never
|
|
738
|
+
: never;
|