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
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
import { Result, ResultOk, ResultErr, Ok, Err, FlattenResult } from './result';
|
|
2
|
+
import { Option, Some, None } from './option';
|
|
3
|
+
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Type Utilities
|
|
6
|
+
// ============================================================================
|
|
7
|
+
|
|
8
|
+
/** Unwrap the inner Result from a ResultAsync */
|
|
9
|
+
export type UnwrapResultAsync<R> = R extends ResultAsync<infer T, infer E>
|
|
10
|
+
? Result<T, E>
|
|
11
|
+
: never;
|
|
12
|
+
|
|
13
|
+
/** Type for functions that may be sync or async */
|
|
14
|
+
type MaybeAsync<T> = T | Promise<T>;
|
|
15
|
+
|
|
16
|
+
/** Type for Result that may be wrapped in Promise */
|
|
17
|
+
type MaybeAsyncResult<T, E> = MaybeAsync<Result<T, E>>;
|
|
18
|
+
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// ResultAsync Class
|
|
21
|
+
// ============================================================================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A wrapper around `Promise<Result<T, E>>` that enables fluent async chains.
|
|
25
|
+
*
|
|
26
|
+
* `ResultAsync` allows you to chain operations on async Results without
|
|
27
|
+
* needing to await at each step. All transformations are lazily composed
|
|
28
|
+
* and only executed when you await the final result.
|
|
29
|
+
*
|
|
30
|
+
* @typeParam T - The success value type
|
|
31
|
+
* @typeParam E - The error value type
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* const result = await ResultAsync.fromPromise(fetch('/api/user'))
|
|
36
|
+
* .andThen(res => ResultAsync.fromPromise(res.json()))
|
|
37
|
+
* .map(data => data.name)
|
|
38
|
+
* .mapErr(e => new ApiError(e.message));
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export class ResultAsync<T, E> implements PromiseLike<Result<T, E>> {
|
|
42
|
+
private constructor(private readonly promise: Promise<Result<T, E>>) {}
|
|
43
|
+
|
|
44
|
+
// ========================================================================
|
|
45
|
+
// Constructors
|
|
46
|
+
// ========================================================================
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Creates a ResultAsync from an existing Promise<Result<T, E>>.
|
|
50
|
+
*/
|
|
51
|
+
static fromResult<T, E>(result: MaybeAsyncResult<T, E>): ResultAsync<T, E> {
|
|
52
|
+
return new ResultAsync(Promise.resolve(result));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Creates a successful ResultAsync containing the value.
|
|
57
|
+
*/
|
|
58
|
+
static ok<T, E = never>(value: T): ResultAsync<T, E> {
|
|
59
|
+
return new ResultAsync(Promise.resolve(Ok(value) as Result<T, E>));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Creates a failed ResultAsync containing the error.
|
|
64
|
+
*/
|
|
65
|
+
static err<T = never, E = unknown>(error: E): ResultAsync<T, E> {
|
|
66
|
+
return new ResultAsync(Promise.resolve(Err(error) as Result<T, E>));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Wraps a Promise, converting resolution to Ok and rejection to Err.
|
|
71
|
+
*
|
|
72
|
+
* @param promise - The promise to wrap
|
|
73
|
+
* @param errorFn - Optional function to transform the caught error
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```typescript
|
|
77
|
+
* const result = await ResultAsync.fromPromise(
|
|
78
|
+
* fetch('/api/data'),
|
|
79
|
+
* (e) => new NetworkError(e)
|
|
80
|
+
* );
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
static fromPromise<T, E = Error>(
|
|
84
|
+
promise: Promise<T>,
|
|
85
|
+
errorFn?: (error: unknown) => E
|
|
86
|
+
): ResultAsync<T, E> {
|
|
87
|
+
return new ResultAsync(
|
|
88
|
+
promise
|
|
89
|
+
.then((value) => Ok(value) as Result<T, E>)
|
|
90
|
+
.catch((error) => {
|
|
91
|
+
const mappedError = errorFn ? errorFn(error) : (error as E);
|
|
92
|
+
return Err(mappedError) as Result<T, E>;
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Wraps a function that may throw, executing it and capturing the result.
|
|
99
|
+
*
|
|
100
|
+
* Supports both sync and async functions.
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```typescript
|
|
104
|
+
* const result = await ResultAsync.try(() => JSON.parse(jsonString));
|
|
105
|
+
* const asyncResult = await ResultAsync.try(async () => {
|
|
106
|
+
* const res = await fetch('/api');
|
|
107
|
+
* return res.json();
|
|
108
|
+
* });
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
static try<T, E = Error>(
|
|
112
|
+
fn: () => MaybeAsync<T>,
|
|
113
|
+
errorFn?: (error: unknown) => E
|
|
114
|
+
): ResultAsync<T, E> {
|
|
115
|
+
return new ResultAsync(
|
|
116
|
+
(async () => {
|
|
117
|
+
try {
|
|
118
|
+
const value = await fn();
|
|
119
|
+
return Ok(value) as Result<T, E>;
|
|
120
|
+
} catch (error) {
|
|
121
|
+
const mappedError = errorFn ? errorFn(error) : (error as E);
|
|
122
|
+
return Err(mappedError) as Result<T, E>;
|
|
123
|
+
}
|
|
124
|
+
})()
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Combines multiple ResultAsync values into a single ResultAsync containing an array.
|
|
130
|
+
*
|
|
131
|
+
* Short-circuits on the first error encountered.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* ```typescript
|
|
135
|
+
* const results = await ResultAsync.all([
|
|
136
|
+
* ResultAsync.fromPromise(fetch('/api/a')),
|
|
137
|
+
* ResultAsync.fromPromise(fetch('/api/b')),
|
|
138
|
+
* ResultAsync.fromPromise(fetch('/api/c')),
|
|
139
|
+
* ]);
|
|
140
|
+
* // Result<[Response, Response, Response], Error>
|
|
141
|
+
* ```
|
|
142
|
+
*/
|
|
143
|
+
static all<T extends readonly ResultAsync<any, any>[]>(
|
|
144
|
+
results: T
|
|
145
|
+
): ResultAsync<
|
|
146
|
+
{ [K in keyof T]: T[K] extends ResultAsync<infer U, any> ? U : never },
|
|
147
|
+
{ [K in keyof T]: T[K] extends ResultAsync<any, infer E> ? E : never }[number]
|
|
148
|
+
> {
|
|
149
|
+
return new ResultAsync(
|
|
150
|
+
(async () => {
|
|
151
|
+
const values: any[] = [];
|
|
152
|
+
for (const resultAsync of results) {
|
|
153
|
+
const result = await resultAsync;
|
|
154
|
+
if (result.is_err()) {
|
|
155
|
+
return result as any;
|
|
156
|
+
}
|
|
157
|
+
values.push(result.value);
|
|
158
|
+
}
|
|
159
|
+
return Ok(values) as any;
|
|
160
|
+
})()
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Combines multiple ResultAsync values, collecting all errors if any fail.
|
|
166
|
+
*
|
|
167
|
+
* Unlike `all`, this doesn't short-circuit and will collect all errors.
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* ```typescript
|
|
171
|
+
* const results = await ResultAsync.allSettled([
|
|
172
|
+
* ResultAsync.fromPromise(fetch('/api/a')),
|
|
173
|
+
* ResultAsync.fromPromise(fetch('/api/b')),
|
|
174
|
+
* ]);
|
|
175
|
+
* // Result<[Response, Response], Error[]>
|
|
176
|
+
* ```
|
|
177
|
+
*/
|
|
178
|
+
static allSettled<T extends readonly ResultAsync<any, any>[]>(
|
|
179
|
+
results: T
|
|
180
|
+
): ResultAsync<
|
|
181
|
+
{ [K in keyof T]: T[K] extends ResultAsync<infer U, any> ? U : never },
|
|
182
|
+
Array<{ [K in keyof T]: T[K] extends ResultAsync<any, infer E> ? E : never }[number]>
|
|
183
|
+
> {
|
|
184
|
+
return new ResultAsync(
|
|
185
|
+
(async () => {
|
|
186
|
+
const values: any[] = [];
|
|
187
|
+
const errors: any[] = [];
|
|
188
|
+
|
|
189
|
+
for (const resultAsync of results) {
|
|
190
|
+
const result = await resultAsync;
|
|
191
|
+
if (result.is_err()) {
|
|
192
|
+
errors.push(result.value);
|
|
193
|
+
} else {
|
|
194
|
+
values.push(result.value);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (errors.length > 0) {
|
|
199
|
+
return Err(errors) as any;
|
|
200
|
+
}
|
|
201
|
+
return Ok(values) as any;
|
|
202
|
+
})()
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// ========================================================================
|
|
207
|
+
// PromiseLike Implementation
|
|
208
|
+
// ========================================================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Implements PromiseLike so ResultAsync can be awaited directly.
|
|
212
|
+
*/
|
|
213
|
+
then<TResult1 = Result<T, E>, TResult2 = never>(
|
|
214
|
+
onfulfilled?: ((value: Result<T, E>) => TResult1 | PromiseLike<TResult1>) | null,
|
|
215
|
+
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | null
|
|
216
|
+
): Promise<TResult1 | TResult2> {
|
|
217
|
+
return this.promise.then(onfulfilled, onrejected);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Returns the underlying Promise.
|
|
222
|
+
*/
|
|
223
|
+
toPromise(): Promise<Result<T, E>> {
|
|
224
|
+
return this.promise;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ========================================================================
|
|
228
|
+
// Type Checking
|
|
229
|
+
// ========================================================================
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Async version of is_ok(). Resolves to true if the result is Ok.
|
|
233
|
+
*/
|
|
234
|
+
async isOk(): Promise<boolean> {
|
|
235
|
+
const result = await this.promise;
|
|
236
|
+
return result.is_ok();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Async version of is_err(). Resolves to true if the result is Err.
|
|
241
|
+
*/
|
|
242
|
+
async isErr(): Promise<boolean> {
|
|
243
|
+
const result = await this.promise;
|
|
244
|
+
return result.is_err();
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ========================================================================
|
|
248
|
+
// Transformations
|
|
249
|
+
// ========================================================================
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Maps the Ok value using a sync or async function.
|
|
253
|
+
*
|
|
254
|
+
* @example
|
|
255
|
+
* ```typescript
|
|
256
|
+
* const result = ResultAsync.ok(5)
|
|
257
|
+
* .map(x => x * 2)
|
|
258
|
+
* .map(async x => x + 1);
|
|
259
|
+
* ```
|
|
260
|
+
*/
|
|
261
|
+
map<U>(fn: (value: T) => MaybeAsync<U>): ResultAsync<U, E> {
|
|
262
|
+
return new ResultAsync(
|
|
263
|
+
this.promise.then(async (result) => {
|
|
264
|
+
if (result.is_ok()) {
|
|
265
|
+
const newValue = await fn(result.value);
|
|
266
|
+
return Ok(newValue) as Result<U, E>;
|
|
267
|
+
}
|
|
268
|
+
return Err(result.value) as Result<U, E>;
|
|
269
|
+
})
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Maps the Err value using a sync or async function.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```typescript
|
|
278
|
+
* const result = ResultAsync.err(new Error('fail'))
|
|
279
|
+
* .mapErr(e => new CustomError(e.message));
|
|
280
|
+
* ```
|
|
281
|
+
*/
|
|
282
|
+
mapErr<F>(fn: (error: E) => MaybeAsync<F>): ResultAsync<T, F> {
|
|
283
|
+
return new ResultAsync(
|
|
284
|
+
this.promise.then(async (result) => {
|
|
285
|
+
if (result.is_err()) {
|
|
286
|
+
const newError = await fn(result.value);
|
|
287
|
+
return Err(newError) as Result<T, F>;
|
|
288
|
+
}
|
|
289
|
+
return Ok(result.value) as Result<T, F>;
|
|
290
|
+
})
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Maps the Ok value, returning a default if Err.
|
|
296
|
+
*/
|
|
297
|
+
async mapOr<U>(defaultValue: U, fn: (value: T) => MaybeAsync<U>): Promise<U> {
|
|
298
|
+
const result = await this.promise;
|
|
299
|
+
if (result.is_ok()) {
|
|
300
|
+
return fn(result.value);
|
|
301
|
+
}
|
|
302
|
+
return defaultValue;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Maps the Ok value, computing a default from the error if Err.
|
|
307
|
+
*/
|
|
308
|
+
async mapOrElse<U>(
|
|
309
|
+
defaultFn: (error: E) => MaybeAsync<U>,
|
|
310
|
+
fn: (value: T) => MaybeAsync<U>
|
|
311
|
+
): Promise<U> {
|
|
312
|
+
const result = await this.promise;
|
|
313
|
+
if (result.is_ok()) {
|
|
314
|
+
return fn(result.value);
|
|
315
|
+
}
|
|
316
|
+
return defaultFn(result.value);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// ========================================================================
|
|
320
|
+
// Chaining (Monadic Operations)
|
|
321
|
+
// ========================================================================
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Chains a function that returns a Result or ResultAsync.
|
|
325
|
+
*
|
|
326
|
+
* This is the monadic bind (flatMap) operation for async Results.
|
|
327
|
+
*
|
|
328
|
+
* @example
|
|
329
|
+
* ```typescript
|
|
330
|
+
* const result = ResultAsync.ok(userId)
|
|
331
|
+
* .andThen(id => fetchUser(id)) // Returns ResultAsync
|
|
332
|
+
* .andThen(user => validateUser(user)); // Returns Result
|
|
333
|
+
* ```
|
|
334
|
+
*/
|
|
335
|
+
andThen<U>(
|
|
336
|
+
fn: (value: T) => MaybeAsync<Result<U, E>> | ResultAsync<U, E>
|
|
337
|
+
): ResultAsync<U, E> {
|
|
338
|
+
return new ResultAsync(
|
|
339
|
+
this.promise.then(async (result) => {
|
|
340
|
+
if (result.is_ok()) {
|
|
341
|
+
const next = fn(result.value);
|
|
342
|
+
if (next instanceof ResultAsync) {
|
|
343
|
+
return next.promise;
|
|
344
|
+
}
|
|
345
|
+
return next;
|
|
346
|
+
}
|
|
347
|
+
return Err(result.value) as Result<U, E>;
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Chains a function that returns a Result or ResultAsync on error.
|
|
354
|
+
*
|
|
355
|
+
* Used for error recovery.
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* ```typescript
|
|
359
|
+
* const result = fetchFromPrimary()
|
|
360
|
+
* .orElse(err => fetchFromBackup())
|
|
361
|
+
* .orElse(err => ResultAsync.ok(defaultValue));
|
|
362
|
+
* ```
|
|
363
|
+
*/
|
|
364
|
+
orElse<F>(
|
|
365
|
+
fn: (error: E) => MaybeAsync<Result<T, F>> | ResultAsync<T, F>
|
|
366
|
+
): ResultAsync<T, F> {
|
|
367
|
+
return new ResultAsync(
|
|
368
|
+
this.promise.then(async (result) => {
|
|
369
|
+
if (result.is_err()) {
|
|
370
|
+
const next = fn(result.value);
|
|
371
|
+
if (next instanceof ResultAsync) {
|
|
372
|
+
return next.promise;
|
|
373
|
+
}
|
|
374
|
+
return next;
|
|
375
|
+
}
|
|
376
|
+
return Ok(result.value) as Result<T, F>;
|
|
377
|
+
})
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Returns the provided ResultAsync if this is Ok, otherwise returns this Err.
|
|
383
|
+
*/
|
|
384
|
+
and<U>(other: ResultAsync<U, E>): ResultAsync<U, E> {
|
|
385
|
+
return new ResultAsync(
|
|
386
|
+
this.promise.then(async (result) => {
|
|
387
|
+
if (result.is_ok()) {
|
|
388
|
+
return other.promise;
|
|
389
|
+
}
|
|
390
|
+
return Err(result.value) as Result<U, E>;
|
|
391
|
+
})
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/**
|
|
396
|
+
* Returns this if Ok, otherwise returns the provided ResultAsync.
|
|
397
|
+
*/
|
|
398
|
+
or<F>(other: ResultAsync<T, F>): ResultAsync<T, F> {
|
|
399
|
+
return new ResultAsync(
|
|
400
|
+
this.promise.then(async (result) => {
|
|
401
|
+
if (result.is_err()) {
|
|
402
|
+
return other.promise;
|
|
403
|
+
}
|
|
404
|
+
return Ok(result.value) as Result<T, F>;
|
|
405
|
+
})
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// ========================================================================
|
|
410
|
+
// Unwrapping
|
|
411
|
+
// ========================================================================
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Unwraps the Ok value, or throws with the provided message.
|
|
415
|
+
*/
|
|
416
|
+
async expect(msg: string): Promise<T> {
|
|
417
|
+
const result = await this.promise;
|
|
418
|
+
return result.expect(msg);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Unwraps the Ok value, or throws.
|
|
423
|
+
*/
|
|
424
|
+
async unwrap(): Promise<T> {
|
|
425
|
+
const result = await this.promise;
|
|
426
|
+
return result.unwrap();
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Unwraps the Err value, or throws with the provided message.
|
|
431
|
+
*/
|
|
432
|
+
async expectErr(msg: string): Promise<E> {
|
|
433
|
+
const result = await this.promise;
|
|
434
|
+
return result.expect_err(msg);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Unwraps the Err value, or throws.
|
|
439
|
+
*/
|
|
440
|
+
async unwrapErr(): Promise<E> {
|
|
441
|
+
const result = await this.promise;
|
|
442
|
+
return result.unwrap_err();
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Unwraps the Ok value, or returns the provided default.
|
|
447
|
+
*/
|
|
448
|
+
async unwrapOr(defaultValue: T): Promise<T> {
|
|
449
|
+
const result = await this.promise;
|
|
450
|
+
return result.unwrap_or(defaultValue);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Unwraps the Ok value, or computes a default from the error.
|
|
455
|
+
*/
|
|
456
|
+
async unwrapOrElse(fn: (error: E) => MaybeAsync<T>): Promise<T> {
|
|
457
|
+
const result = await this.promise;
|
|
458
|
+
if (result.is_ok()) {
|
|
459
|
+
return result.value;
|
|
460
|
+
}
|
|
461
|
+
return fn(result.value);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ========================================================================
|
|
465
|
+
// Inspection
|
|
466
|
+
// ========================================================================
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Calls the function with the Ok value for side effects.
|
|
470
|
+
*/
|
|
471
|
+
inspect(fn: (value: T) => void): ResultAsync<T, E> {
|
|
472
|
+
return new ResultAsync(
|
|
473
|
+
this.promise.then((result) => {
|
|
474
|
+
if (result.is_ok()) {
|
|
475
|
+
fn(result.value);
|
|
476
|
+
}
|
|
477
|
+
return result;
|
|
478
|
+
})
|
|
479
|
+
);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Calls the function with the Err value for side effects.
|
|
484
|
+
*/
|
|
485
|
+
inspectErr(fn: (error: E) => void): ResultAsync<T, E> {
|
|
486
|
+
return new ResultAsync(
|
|
487
|
+
this.promise.then((result) => {
|
|
488
|
+
if (result.is_err()) {
|
|
489
|
+
fn(result.value);
|
|
490
|
+
}
|
|
491
|
+
return result;
|
|
492
|
+
})
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ========================================================================
|
|
497
|
+
// Conversion
|
|
498
|
+
// ========================================================================
|
|
499
|
+
|
|
500
|
+
/**
|
|
501
|
+
* Converts to Option<T>, discarding the error.
|
|
502
|
+
*/
|
|
503
|
+
async ok(): Promise<Option<T>> {
|
|
504
|
+
const result = await this.promise;
|
|
505
|
+
return result.ok();
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
/**
|
|
509
|
+
* Converts to Option<E>, discarding the value.
|
|
510
|
+
*/
|
|
511
|
+
async err(): Promise<Option<E>> {
|
|
512
|
+
const result = await this.promise;
|
|
513
|
+
return result.err();
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Flattens a ResultAsync<Result<U, E>, E> into ResultAsync<U, E>.
|
|
518
|
+
*/
|
|
519
|
+
flatten<U>(this: ResultAsync<Result<U, E>, E>): ResultAsync<U, E> {
|
|
520
|
+
return new ResultAsync(
|
|
521
|
+
this.promise.then((result) => {
|
|
522
|
+
if (result.is_ok()) {
|
|
523
|
+
return result.value;
|
|
524
|
+
}
|
|
525
|
+
return Err(result.value) as Result<U, E>;
|
|
526
|
+
})
|
|
527
|
+
);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// ========================================================================
|
|
531
|
+
// Pattern Matching
|
|
532
|
+
// ========================================================================
|
|
533
|
+
|
|
534
|
+
/**
|
|
535
|
+
* Pattern matches on the result, calling the appropriate handler.
|
|
536
|
+
*
|
|
537
|
+
* @example
|
|
538
|
+
* ```typescript
|
|
539
|
+
* const message = await result.match({
|
|
540
|
+
* Ok: (value) => `Success: ${value}`,
|
|
541
|
+
* Err: (error) => `Failed: ${error.message}`,
|
|
542
|
+
* });
|
|
543
|
+
* ```
|
|
544
|
+
*/
|
|
545
|
+
async match<R>(handlers: {
|
|
546
|
+
Ok: (value: T) => MaybeAsync<R>;
|
|
547
|
+
Err: (error: E) => MaybeAsync<R>;
|
|
548
|
+
}): Promise<R> {
|
|
549
|
+
const result = await this.promise;
|
|
550
|
+
if (result.is_ok()) {
|
|
551
|
+
return handlers.Ok(result.value);
|
|
552
|
+
}
|
|
553
|
+
return handlers.Err(result.value);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// ============================================================================
|
|
558
|
+
// Utility Functions
|
|
559
|
+
// ============================================================================
|
|
560
|
+
|
|
561
|
+
/**
|
|
562
|
+
* Wraps an async function to return ResultAsync instead of throwing.
|
|
563
|
+
*
|
|
564
|
+
* @example
|
|
565
|
+
* ```typescript
|
|
566
|
+
* const safeFetch = tryAsync(fetch);
|
|
567
|
+
* const result = await safeFetch('/api/data');
|
|
568
|
+
* // ResultAsync<Response, Error>
|
|
569
|
+
* ```
|
|
570
|
+
*/
|
|
571
|
+
export function tryAsync<Args extends any[], T, E = Error>(
|
|
572
|
+
fn: (...args: Args) => Promise<T>,
|
|
573
|
+
errorFn?: (error: unknown) => E
|
|
574
|
+
): (...args: Args) => ResultAsync<T, E> {
|
|
575
|
+
return (...args: Args) => ResultAsync.try(() => fn(...args), errorFn);
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Converts a sync Result-returning function to return ResultAsync.
|
|
580
|
+
*
|
|
581
|
+
* @example
|
|
582
|
+
* ```typescript
|
|
583
|
+
* const parseJson = (s: string): Result<unknown, Error> => { ... };
|
|
584
|
+
* const parseJsonAsync = liftAsync(parseJson);
|
|
585
|
+
* const result = await parseJsonAsync(jsonString);
|
|
586
|
+
* ```
|
|
587
|
+
*/
|
|
588
|
+
export function liftAsync<Args extends any[], T, E>(
|
|
589
|
+
fn: (...args: Args) => Result<T, E>
|
|
590
|
+
): (...args: Args) => ResultAsync<T, E> {
|
|
591
|
+
return (...args: Args) => ResultAsync.fromResult(fn(...args));
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
/**
|
|
595
|
+
* Runs multiple ResultAsync operations in parallel and collects results.
|
|
596
|
+
*
|
|
597
|
+
* Alias for ResultAsync.all with better ergonomics for inline use.
|
|
598
|
+
*/
|
|
599
|
+
export const allAsync = ResultAsync.all.bind(ResultAsync);
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Converts a Promise to a ResultAsync.
|
|
603
|
+
*
|
|
604
|
+
* Alias for ResultAsync.fromPromise.
|
|
605
|
+
*/
|
|
606
|
+
export const fromPromise = ResultAsync.fromPromise.bind(ResultAsync);
|
|
607
|
+
|
|
608
|
+
// ============================================================================
|
|
609
|
+
// Extension: Add toAsync() method to Result
|
|
610
|
+
// ============================================================================
|
|
611
|
+
|
|
612
|
+
declare module './result' {
|
|
613
|
+
interface ResultOk<T, E> {
|
|
614
|
+
/**
|
|
615
|
+
* Lifts this Result into a ResultAsync.
|
|
616
|
+
*/
|
|
617
|
+
toAsync(): ResultAsync<T, E>;
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
interface ResultErr<T, E> {
|
|
621
|
+
/**
|
|
622
|
+
* Lifts this Result into a ResultAsync.
|
|
623
|
+
*/
|
|
624
|
+
toAsync(): ResultAsync<T, E>;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Add toAsync to Result classes
|
|
629
|
+
ResultOk.prototype.toAsync = function <T, E>(this: ResultOk<T, E>): ResultAsync<T, E> {
|
|
630
|
+
return ResultAsync.fromResult(this);
|
|
631
|
+
};
|
|
632
|
+
|
|
633
|
+
ResultErr.prototype.toAsync = function <T, E>(this: ResultErr<T, E>): ResultAsync<T, E> {
|
|
634
|
+
return ResultAsync.fromResult(this);
|
|
635
|
+
};
|
package/src/result.test.ts
CHANGED
|
@@ -442,4 +442,40 @@ describe('Result', () => {
|
|
|
442
442
|
});
|
|
443
443
|
});
|
|
444
444
|
});
|
|
445
|
+
|
|
446
|
+
describe('New Rust-inspired Methods', () => {
|
|
447
|
+
describe('transpose', () => {
|
|
448
|
+
it('transposes Ok(Some(v)) to Some(Ok(v))', () => {
|
|
449
|
+
const { Some } = require('./option');
|
|
450
|
+
const result = Ok(Some(42));
|
|
451
|
+
const transposed = result.transpose();
|
|
452
|
+
expect(transposed.is_some()).toBe(true);
|
|
453
|
+
expect(transposed.unwrap().is_ok()).toBe(true);
|
|
454
|
+
expect(transposed.unwrap().unwrap()).toBe(42);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
it('transposes Ok(None) to None', () => {
|
|
458
|
+
const { None } = require('./option');
|
|
459
|
+
const result = Ok(None());
|
|
460
|
+
const transposed = result.transpose();
|
|
461
|
+
expect(transposed.is_none()).toBe(true);
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
it('transposes Err(e) to Some(Err(e))', () => {
|
|
465
|
+
const result = Err('error') as Result<any, string>;
|
|
466
|
+
const transposed = result.transpose();
|
|
467
|
+
expect(transposed.is_some()).toBe(true);
|
|
468
|
+
expect(transposed.unwrap().is_err()).toBe(true);
|
|
469
|
+
expect(transposed.unwrap().unwrap_err()).toBe('error');
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('works with complex nested types', () => {
|
|
473
|
+
const { Some } = require('./option');
|
|
474
|
+
const result = Ok(Some({ id: 1, name: 'test' }));
|
|
475
|
+
const transposed = result.transpose();
|
|
476
|
+
expect(transposed.is_some()).toBe(true);
|
|
477
|
+
expect(transposed.unwrap().unwrap()).toEqual({ id: 1, name: 'test' });
|
|
478
|
+
});
|
|
479
|
+
});
|
|
480
|
+
});
|
|
445
481
|
});
|