@oatscenter/result 0.100.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/result.js ADDED
@@ -0,0 +1,1062 @@
1
+ /**
2
+ * A class-based Result<T, E> system inspired by Rust's Result type.
3
+ *
4
+ * This module provides Result and AsyncResult classes for elegant error handling
5
+ * with method chaining and the `take()` pattern for early returns.
6
+ *
7
+ * @module @oatscenter/result
8
+ */
9
+ import { UnexpectedError } from "./error.js";
10
+ function isOkValue(value) {
11
+ return value.success;
12
+ }
13
+ function isErrValue(value) {
14
+ return !value.success;
15
+ }
16
+ /**
17
+ * A synchronous Result class that represents either success (Ok) or failure (Err).
18
+ *
19
+ * Provides method chaining for transformations and the `take()` pattern for
20
+ * unwrapping values with early returns.
21
+ *
22
+ * @template T - The type of the success value
23
+ * @template E - The type of the error (must extend BaseError)
24
+ *
25
+ * @example
26
+ * ```typescript
27
+ * function divide(a: number, b: number): Result<number, ValidationError> {
28
+ * if (b === 0) {
29
+ * return Result.err(new ValidationError("Division by zero"));
30
+ * }
31
+ * return Result.ok(a / b);
32
+ * }
33
+ *
34
+ * const result = divide(10, 2)
35
+ * .map(x => x * 2)
36
+ * .map(x => x + 1);
37
+ *
38
+ * const value = result.take();
39
+ * if (isErr(value)) return value;
40
+ * console.log(value); // 11
41
+ * ```
42
+ */
43
+ export class Result {
44
+ _value;
45
+ constructor(_value) {
46
+ this._value = _value;
47
+ }
48
+ /**
49
+ * Creates a successful Result containing a value.
50
+ *
51
+ * @template T - The type of the success value
52
+ * @template E - The type of the error (defaults to never)
53
+ * @param value - The success value to wrap
54
+ * @returns A Result instance containing the value
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const result = Result.ok(42);
59
+ * const value = result.take();
60
+ * if (!isErr(value)) {
61
+ * console.log(value); // 42
62
+ * }
63
+ * ```
64
+ */
65
+ static ok(value) {
66
+ return new Result({ success: true, value });
67
+ }
68
+ /**
69
+ * Creates a failed Result containing an error.
70
+ *
71
+ * @template E - The type of the error (must extend BaseError)
72
+ * @template T - The type of the success value (defaults to never)
73
+ * @param error - The error to wrap
74
+ * @returns A Result instance containing the error
75
+ *
76
+ * @example
77
+ * ```typescript
78
+ * const result = Result.err(new ValidationError("Invalid input"));
79
+ * const value = result.take();
80
+ * if (isErr(value)) {
81
+ * console.error(value.error.message); // "Invalid input"
82
+ * }
83
+ * ```
84
+ */
85
+ static err(error) {
86
+ return new Result({ success: false, error });
87
+ }
88
+ /**
89
+ * Wraps a function that might throw into a Result.
90
+ *
91
+ * Catches any exceptions and wraps them in UnexpectedError.
92
+ *
93
+ * @template T - The type of the return value
94
+ * @param fn - Function that might throw
95
+ * @param context - Optional context to add to the error
96
+ * @returns Ok with the return value, or Err with UnexpectedError
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const obj = Result.try(() =>
101
+ * typeof data === "string" ? JSON.parse(data) : data
102
+ * );
103
+ *
104
+ * const value = obj.take();
105
+ * if (isErr(value)) {
106
+ * console.error("Parse failed:", value.error);
107
+ * return;
108
+ * }
109
+ * console.log(value); // parsed object
110
+ * ```
111
+ */
112
+ static try(fn, context) {
113
+ try {
114
+ return Result.ok(fn());
115
+ }
116
+ catch (cause) {
117
+ return Result.err(new UnexpectedError({ cause }).withContext(context));
118
+ }
119
+ }
120
+ /**
121
+ * Type guard to check if a value is an Ok Result.
122
+ *
123
+ * @template T - The type of the success value
124
+ * @template E - The type of the error
125
+ * @param result - The Result to check
126
+ * @returns True if the result is Ok, false otherwise
127
+ *
128
+ * @example
129
+ * ```typescript
130
+ * const result = Result.ok(42);
131
+ * if (Result.isOk(result)) {
132
+ * // TypeScript knows result is Ok<number> here
133
+ * }
134
+ * ```
135
+ */
136
+ static isOk(result) {
137
+ return result.isOk();
138
+ }
139
+ static isErr(value) {
140
+ // Check if it's a Result instance
141
+ if (value instanceof Result) {
142
+ return value.isErr();
143
+ }
144
+ // For non-Result values, return false
145
+ return false;
146
+ }
147
+ /**
148
+ * Combines multiple Results into a single Result containing an array.
149
+ *
150
+ * If all Results are Ok, returns Ok with an array of all values.
151
+ * If any Result is Err, returns the first Err encountered.
152
+ *
153
+ * @template T - The type of the Ok values
154
+ * @template E - The type of the errors
155
+ * @param results - Array of Results to combine
156
+ * @returns Ok with array of values, or the first Err
157
+ *
158
+ * @example
159
+ * ```typescript
160
+ * const results = [Result.ok(1), Result.ok(2), Result.ok(3)];
161
+ * const combined = Result.all(results);
162
+ * // Ok([1, 2, 3])
163
+ *
164
+ * const withError = [Result.ok(1), Result.err(new ValidationError("Failed")), Result.ok(3)];
165
+ * const combined2 = Result.all(withError);
166
+ * // Err(ValidationError)
167
+ * ```
168
+ */
169
+ static all(results) {
170
+ const values = [];
171
+ for (const result of results) {
172
+ if (result.isErr()) {
173
+ return result;
174
+ }
175
+ const resultValue = result._unsafeValue();
176
+ if (resultValue.success) {
177
+ values.push(resultValue.value);
178
+ }
179
+ }
180
+ return Result.ok(values);
181
+ }
182
+ /**
183
+ * Returns the first Ok result from an array of Results.
184
+ *
185
+ * If any Result is Ok, returns that Ok result.
186
+ * If all Results are Err, returns the last Err.
187
+ *
188
+ * @template T - The type of the Ok values
189
+ * @template E - The type of the errors
190
+ * @param results - Array of Results to check
191
+ * @returns The first Ok, or the last Err if all failed
192
+ *
193
+ * @example
194
+ * ```typescript
195
+ * const results = [Result.err(new Error("e1")), Result.ok(2), Result.ok(3)];
196
+ * const first = Result.any(results);
197
+ * // Ok(2)
198
+ *
199
+ * const allErrors = [
200
+ * Result.err(new Error("e1")),
201
+ * Result.err(new Error("e2")),
202
+ * Result.err(new Error("e3"))
203
+ * ];
204
+ * const first2 = Result.any(allErrors);
205
+ * // Err(Error("e3")) - the last error
206
+ * ```
207
+ */
208
+ static any(results) {
209
+ for (const result of results) {
210
+ if (result.isOk()) {
211
+ return result;
212
+ }
213
+ }
214
+ const last = results[results.length - 1];
215
+ if (!last) {
216
+ throw new Error("Result.any requires at least one result");
217
+ }
218
+ return last;
219
+ }
220
+ /**
221
+ * Type guard to check if this Result is Ok.
222
+ *
223
+ * @returns True if this result is Ok, false otherwise
224
+ *
225
+ * @example
226
+ * ```typescript
227
+ * const result = Result.ok(42);
228
+ * if (result.isOk()) {
229
+ * // TypeScript knows result is Ok here
230
+ * }
231
+ * ```
232
+ */
233
+ isOk() {
234
+ return this._value.success === true;
235
+ }
236
+ /**
237
+ * Type guard to check if this Result is Err.
238
+ *
239
+ * @returns True if this result is Err, false otherwise
240
+ *
241
+ * @example
242
+ * ```typescript
243
+ * const result = Result.err(new ValidationError("Failed"));
244
+ * if (result.isErr()) {
245
+ * // TypeScript knows result is Err here
246
+ * }
247
+ * ```
248
+ */
249
+ isErr() {
250
+ return this._value.success === false;
251
+ }
252
+ /**
253
+ * Transforms the Ok value using a mapper function, leaving Err untouched.
254
+ *
255
+ * @template U - The type of the transformed value
256
+ * @param fn - Function to transform the Ok value
257
+ * @returns A new Result with the transformed value, or the original Err
258
+ *
259
+ * @example
260
+ * ```typescript
261
+ * const result = Result.ok(5)
262
+ * .map(x => x * 2)
263
+ * .map(x => x.toString());
264
+ *
265
+ * const value = result.take();
266
+ * if (!isErr(value)) {
267
+ * console.log(value); // "10"
268
+ * }
269
+ * ```
270
+ */
271
+ map(fn) {
272
+ const value = this._value;
273
+ if (isOkValue(value)) {
274
+ return Result.ok(fn(value.value));
275
+ }
276
+ return Result.err(value.error);
277
+ }
278
+ /**
279
+ * Transforms the Err value using a mapper function, leaving Ok untouched.
280
+ *
281
+ * @template F - The type of the transformed error
282
+ * @param fn - Function to transform the Err value
283
+ * @returns A new Result with the transformed error, or the original Ok
284
+ *
285
+ * @example
286
+ * ```typescript
287
+ * const result = Result.err(new ValidationError("Failed"))
288
+ * .mapErr(e => new NetworkError({ cause: e }));
289
+ * ```
290
+ */
291
+ mapErr(fn) {
292
+ const value = this._value;
293
+ if (isOkValue(value)) {
294
+ return Result.ok(value.value);
295
+ }
296
+ return Result.err(fn(value.error));
297
+ }
298
+ /**
299
+ * Chains operations that return Results (also known as flatMap).
300
+ *
301
+ * If this Result is Ok, calls the function with the Ok value and returns its Result.
302
+ * If this Result is Err, returns the Err without calling the function.
303
+ *
304
+ * @template U - The type of the new Ok value
305
+ * @template F - The type of the new error
306
+ * @param fn - Function that takes the Ok value and returns a new Result
307
+ * @returns The Result from calling fn, or the original Err
308
+ *
309
+ * @example
310
+ * ```typescript
311
+ * function parseNumber(s: string): Result<number, ValidationError> {
312
+ * const n = Number(s);
313
+ * if (isNaN(n)) {
314
+ * return Result.err(new ValidationError("Not a number"));
315
+ * }
316
+ * return Result.ok(n);
317
+ * }
318
+ *
319
+ * const result = Result.ok("42")
320
+ * .andThen(parseNumber)
321
+ * .map(x => x * 2);
322
+ * ```
323
+ */
324
+ andThen(fn) {
325
+ const value = this._value;
326
+ if (isOkValue(value)) {
327
+ return fn(value.value);
328
+ }
329
+ return Result.err(value.error);
330
+ }
331
+ /**
332
+ * Extracts the value from Ok or returns the Err for early returns.
333
+ *
334
+ * This is the equivalent of Rust's `?` operator. Use it with `isErr()` for
335
+ * early returns in functions that return Results.
336
+ *
337
+ * Returns either:
338
+ * - The unwrapped value T if this result is Ok
339
+ * - An Err Result<never, E> if this result is Err (can be directly returned)
340
+ *
341
+ * @returns The unwrapped value or Err Result
342
+ *
343
+ * @example
344
+ * ```typescript
345
+ * function processData(input: string): Result<number, ValidationError> {
346
+ * const parsed = parseInput(input).take();
347
+ * if (isErr(parsed)) return parsed;
348
+ *
349
+ * const validated = validate(parsed).take();
350
+ * if (isErr(validated)) return validated;
351
+ *
352
+ * return Result.ok(validated * 2);
353
+ * }
354
+ * ```
355
+ */
356
+ take() {
357
+ const value = this._value;
358
+ if (isOkValue(value)) {
359
+ return value.value;
360
+ }
361
+ return Result.err(value.error);
362
+ }
363
+ /**
364
+ * Returns the Ok value or throws the Err error.
365
+ *
366
+ * This is useful at process or demo boundaries where exception-style control
367
+ * flow is acceptable and a caller wants to avoid repetitive `take()` /
368
+ * `isErr(...)` branching.
369
+ *
370
+ * @returns The unwrapped Ok value
371
+ * @throws The contained Err error
372
+ */
373
+ orThrow() {
374
+ const value = this._value;
375
+ if (isOkValue(value)) {
376
+ return value.value;
377
+ }
378
+ throw value.error;
379
+ }
380
+ /**
381
+ * Adds context to an Err result for early returns.
382
+ * Chainable with take() for adding context when propagating errors.
383
+ *
384
+ * @param message - Context message describing the operation that failed
385
+ * @param extra - Optional additional context data
386
+ * @returns This Result with context added to the error
387
+ *
388
+ * @example
389
+ * ```typescript
390
+ * const user = await getUser(id).take();
391
+ * if (isErr(user)) return user.context("failed to fetch user");
392
+ *
393
+ * // With extra data:
394
+ * if (isErr(user)) return user.context("failed to fetch user", { userId: id });
395
+ * ```
396
+ */
397
+ context(message, extra) {
398
+ const value = this._value;
399
+ if (isErrValue(value)) {
400
+ const contextData = extra ? { message, ...extra } : { message };
401
+ value.error.withContext(contextData);
402
+ }
403
+ return this;
404
+ }
405
+ /**
406
+ * Pattern matching for Results - handle both Ok and Err cases.
407
+ *
408
+ * @template U - The type of the return value
409
+ * @param pattern - Object with ok and err handler functions
410
+ * @returns The result of calling either the ok or err handler
411
+ *
412
+ * @example
413
+ * ```typescript
414
+ * const message = result.match({
415
+ * ok: (value) => `Success: ${value}`,
416
+ * err: (error) => `Error: ${error.message}`
417
+ * });
418
+ * ```
419
+ */
420
+ match(pattern) {
421
+ const value = this._value;
422
+ if (isOkValue(value)) {
423
+ return pattern.ok(value.value);
424
+ }
425
+ return pattern.err(value.error);
426
+ }
427
+ /**
428
+ * Returns the Ok value or a default value if Err.
429
+ *
430
+ * @template U - The type of the default value
431
+ * @param defaultValue - The value to return if this result is Err
432
+ * @returns The Ok value or the default value
433
+ *
434
+ * @example
435
+ * ```typescript
436
+ * const value = result.unwrapOr(0);
437
+ * console.log(value); // 42 or 0
438
+ * ```
439
+ */
440
+ unwrapOr(defaultValue) {
441
+ const value = this._value;
442
+ if (isOkValue(value)) {
443
+ return value.value;
444
+ }
445
+ return defaultValue;
446
+ }
447
+ /**
448
+ * Returns the Ok value or computes a default from the error.
449
+ *
450
+ * @template U - The type of the default value
451
+ * @param fn - Function to compute the default value from the error
452
+ * @returns The Ok value or the computed default value
453
+ *
454
+ * @example
455
+ * ```typescript
456
+ * const value = result.unwrapOrElse(error => {
457
+ * console.error(error);
458
+ * return 0;
459
+ * });
460
+ * ```
461
+ */
462
+ unwrapOrElse(fn) {
463
+ const value = this._value;
464
+ if (isOkValue(value)) {
465
+ return value.value;
466
+ }
467
+ return fn(value.error);
468
+ }
469
+ /**
470
+ * Returns this result if Ok, otherwise returns the fallback result.
471
+ *
472
+ * @template U - The type of the fallback Ok value
473
+ * @param other - The fallback Result to use if this is Err
474
+ * @returns This result if Ok, otherwise the fallback
475
+ *
476
+ * @example
477
+ * ```typescript
478
+ * const result = fetchFromCache()
479
+ * .or(fetchFromDatabase())
480
+ * .or(fetchFromAPI());
481
+ * ```
482
+ */
483
+ or(other) {
484
+ if (isOkValue(this._value)) {
485
+ return this;
486
+ }
487
+ return other;
488
+ }
489
+ /**
490
+ * Returns this result if Ok, otherwise computes a fallback from the error.
491
+ *
492
+ * @template R - The Result type returned by the fallback function
493
+ * @param fn - Function to compute a fallback Result from the error
494
+ * @returns This result if Ok, otherwise the computed fallback
495
+ *
496
+ * @example
497
+ * ```typescript
498
+ * const result = fetchData().orElse(error => {
499
+ * console.warn("Primary failed, trying backup");
500
+ * return fetchBackup();
501
+ * });
502
+ * ```
503
+ */
504
+ orElse(fn) {
505
+ const value = this._value;
506
+ if (isOkValue(value)) {
507
+ return Result.ok(value.value);
508
+ }
509
+ return fn(value.error);
510
+ }
511
+ /**
512
+ * Performs a side effect on the Ok value without changing the Result.
513
+ *
514
+ * @param fn - Function to call with the Ok value (if Ok)
515
+ * @returns This Result, unchanged
516
+ *
517
+ * @example
518
+ * ```typescript
519
+ * const result = fetchUser("123")
520
+ * .inspect(user => console.log("Fetched:", user))
521
+ * .map(user => user.name);
522
+ * ```
523
+ */
524
+ inspect(fn) {
525
+ const value = this._value;
526
+ if (isOkValue(value)) {
527
+ fn(value.value);
528
+ }
529
+ return this;
530
+ }
531
+ /**
532
+ * Performs a side effect on the Err value without changing the Result.
533
+ *
534
+ * @param fn - Function to call with the error value (if Err)
535
+ * @returns This Result, unchanged
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * const result = fetchUser("123")
540
+ * .inspectErr(error => console.error("Failed:", error))
541
+ * .map(user => user.name);
542
+ * ```
543
+ */
544
+ inspectErr(fn) {
545
+ const value = this._value;
546
+ if (isErrValue(value)) {
547
+ fn(value.error);
548
+ }
549
+ return this;
550
+ }
551
+ /**
552
+ * Gets the error from an Err Result.
553
+ *
554
+ * Only call this after checking `isErr()`. If called on Ok, throws an error.
555
+ *
556
+ * @returns The error value
557
+ *
558
+ * @example
559
+ * ```typescript
560
+ * const result = err(new ValidationError("Failed"));
561
+ * if (result.isErr()) {
562
+ * console.log(result.error.message); // "Failed"
563
+ * }
564
+ * ```
565
+ */
566
+ get error() {
567
+ const value = this._value;
568
+ if (isErrValue(value)) {
569
+ return value.error;
570
+ }
571
+ throw new Error("Called .error on an Ok Result");
572
+ }
573
+ /**
574
+ * Internal method to get the raw value (for testing/debugging).
575
+ * Not recommended for general use - prefer take() instead.
576
+ */
577
+ _unsafeValue() {
578
+ return this._value;
579
+ }
580
+ }
581
+ /**
582
+ * An asynchronous Result class that represents a Promise of Result<T, E>.
583
+ *
584
+ * Implements PromiseLike to be awaitable, and provides async versions of
585
+ * all Result methods that return AsyncResult for seamless chaining.
586
+ *
587
+ * @template T - The type of the success value
588
+ * @template E - The type of the error (must extend BaseError)
589
+ *
590
+ * @example
591
+ * ```typescript
592
+ * async function fetchUser(id: string): AsyncResult<User, NetworkError> {
593
+ * return AsyncResult.wrap(async () => {
594
+ * const response = await fetch(`/api/users/${id}`);
595
+ * return await response.json();
596
+ * });
597
+ * }
598
+ *
599
+ * const result = fetchUser("123")
600
+ * .map(user => user.name)
601
+ * .map(name => name.toUpperCase());
602
+ *
603
+ * const value = await result.take();
604
+ * if (isErr(value)) return value;
605
+ * console.log(value); // "ALICE"
606
+ * ```
607
+ */
608
+ export class AsyncResult {
609
+ promise;
610
+ constructor(promise) {
611
+ this.promise = promise;
612
+ }
613
+ /**
614
+ * Creates an AsyncResult from a Promise of Result.
615
+ *
616
+ * @template T - The type of the success value
617
+ * @template E - The type of the error
618
+ * @param promise - The promise that resolves to a Result
619
+ * @returns An AsyncResult wrapping the promise
620
+ *
621
+ * @example
622
+ * ```typescript
623
+ * const asyncResult = AsyncResult.from(fetchData());
624
+ * ```
625
+ */
626
+ static from(promise) {
627
+ return new AsyncResult(promise);
628
+ }
629
+ /**
630
+ * Creates a successful AsyncResult with the given value.
631
+ *
632
+ * @template T - The type of the Ok value
633
+ * @template E - The type of the error (defaults to never)
634
+ * @param value - The value to wrap in an Ok AsyncResult
635
+ * @returns AsyncResult in the Ok state
636
+ *
637
+ * @example
638
+ * ```typescript
639
+ * const result = AsyncResult.ok(42);
640
+ * const value = await result.take();
641
+ * console.log(value); // 42
642
+ * ```
643
+ */
644
+ static ok(value) {
645
+ return new AsyncResult(Promise.resolve(Result.ok(value)));
646
+ }
647
+ /**
648
+ * Creates a failed AsyncResult with the given error.
649
+ *
650
+ * @template E - The type of the error
651
+ * @template T - The type of the Ok value (defaults to never)
652
+ * @param error - The error to wrap in an Err AsyncResult
653
+ * @returns AsyncResult in the Err state
654
+ *
655
+ * @example
656
+ * ```typescript
657
+ * const result = AsyncResult.err(new ValidationError("Invalid input"));
658
+ * const value = await result.take();
659
+ * if (Result.isErr(value)) {
660
+ * console.error(value.error.message); // "Invalid input"
661
+ * }
662
+ * ```
663
+ */
664
+ static err(error) {
665
+ return new AsyncResult(Promise.resolve(Result.err(error)));
666
+ }
667
+ /**
668
+ * Creates an AsyncResult from a Result, AsyncResult, or Promise<Result>.
669
+ *
670
+ * This is the key method for working with MaybeAsync types - it normalizes
671
+ * both synchronous Results and asynchronous AsyncResults into AsyncResults.
672
+ *
673
+ * @template T - The type of the success value
674
+ * @template E - The type of the error
675
+ * @param value - A Result, AsyncResult, or Promise<Result>
676
+ * @returns An AsyncResult
677
+ *
678
+ * @example
679
+ * ```typescript
680
+ * const asyncResult = AsyncResult.lift(Result.ok(42));
681
+ * const asyncResult2 = AsyncResult.lift(Promise.resolve(Result.ok(42)));
682
+ * const asyncResult3 = AsyncResult.lift(existingAsyncResult); // Pass-through
683
+ * ```
684
+ */
685
+ static lift(value) {
686
+ if (value instanceof AsyncResult) {
687
+ return value; // Already an AsyncResult, just return it
688
+ }
689
+ if (value instanceof Promise) {
690
+ return new AsyncResult(value);
691
+ }
692
+ return new AsyncResult(Promise.resolve(value));
693
+ }
694
+ /**
695
+ * Wraps an async function that might throw into an AsyncResult.
696
+ *
697
+ * Catches any exceptions and wraps them in UnexpectedError.
698
+ *
699
+ * @template T - The type of the return value
700
+ * @param fn - Async function that might throw
701
+ * @param context - Optional context to add to the error
702
+ * @returns AsyncResult with the return value or UnexpectedError
703
+ *
704
+ * @example
705
+ * ```typescript
706
+ * const user = AsyncResult.try(async () => {
707
+ * const response = await fetch("/api/user");
708
+ * return await response.json();
709
+ * });
710
+ *
711
+ * const value = await user.take();
712
+ * if (isErr(value)) {
713
+ * console.error("Fetch failed:", value.error);
714
+ * return;
715
+ * }
716
+ * console.log(value); // user object
717
+ * ```
718
+ */
719
+ static try(fn, context) {
720
+ return new AsyncResult((async () => {
721
+ try {
722
+ const value = await fn();
723
+ return Result.ok(value);
724
+ }
725
+ catch (cause) {
726
+ return Result.err(new UnexpectedError({ cause }).withContext(context));
727
+ }
728
+ })());
729
+ }
730
+ /**
731
+ * Combines multiple AsyncResults into a single AsyncResult containing an array.
732
+ *
733
+ * If all Results are Ok, returns Ok with an array of all values.
734
+ * If any Result is Err, returns the first Err encountered.
735
+ *
736
+ * @template T - The type of the Ok values
737
+ * @template E - The type of the errors
738
+ * @param results - Array of AsyncResults or Promises to combine
739
+ * @returns AsyncResult with array of values, or the first Err
740
+ *
741
+ * @example
742
+ * ```typescript
743
+ * const users = await AsyncResult.all([
744
+ * fetchUser("1"),
745
+ * fetchUser("2"),
746
+ * fetchUser("3")
747
+ * ]).take();
748
+ *
749
+ * if (Result.isErr(users)) {
750
+ * console.error("Failed to fetch users");
751
+ * } else {
752
+ * console.log(users); // [user1, user2, user3]
753
+ * }
754
+ * ```
755
+ */
756
+ static all(results) {
757
+ return new AsyncResult((async () => {
758
+ const resolvedResults = await Promise.all(results.map(async (r) => {
759
+ if (r instanceof AsyncResult) {
760
+ const res = await r;
761
+ return res;
762
+ }
763
+ return r;
764
+ }));
765
+ const values = [];
766
+ for (const result of resolvedResults) {
767
+ if (result.isErr()) {
768
+ return result;
769
+ }
770
+ const resultValue = result._unsafeValue();
771
+ if (resultValue.success) {
772
+ values.push(resultValue.value);
773
+ }
774
+ }
775
+ return Result.ok(values);
776
+ })());
777
+ }
778
+ /**
779
+ * Returns the first Ok result from an array of AsyncResults.
780
+ *
781
+ * If any Result is Ok, returns that Ok result.
782
+ * If all Results are Err, returns the last Err.
783
+ *
784
+ * @template T - The type of the Ok values
785
+ * @template E - The type of the errors
786
+ * @param results - Array of AsyncResults or Promises to check
787
+ * @returns AsyncResult with the first Ok, or the last Err
788
+ *
789
+ * @example
790
+ * ```typescript
791
+ * const data = await AsyncResult.any([
792
+ * fetchFromPrimary(),
793
+ * fetchFromSecondary(),
794
+ * fetchFromBackup()
795
+ * ]).take();
796
+ *
797
+ * if (Result.isErr(data)) {
798
+ * console.error("All sources failed");
799
+ * } else {
800
+ * console.log(data); // First successful result
801
+ * }
802
+ * ```
803
+ */
804
+ static any(results) {
805
+ return new AsyncResult((async () => {
806
+ const resolvedResults = await Promise.all(results.map(async (r) => {
807
+ if (r instanceof AsyncResult) {
808
+ const res = await r;
809
+ return res;
810
+ }
811
+ return r;
812
+ }));
813
+ for (const result of resolvedResults) {
814
+ if (result.isOk()) {
815
+ return result;
816
+ }
817
+ }
818
+ const last = resolvedResults[resolvedResults.length - 1];
819
+ if (!last) {
820
+ throw new Error("AsyncResult.any requires at least one result");
821
+ }
822
+ return last;
823
+ })());
824
+ }
825
+ /**
826
+ * Implements PromiseLike to make AsyncResult awaitable.
827
+ *
828
+ * @template TResult1 - The type when fulfilled
829
+ * @template TResult2 - The type when rejected
830
+ * @param onfulfilled - Callback for when the promise is fulfilled
831
+ * @param onrejected - Callback for when the promise is rejected
832
+ * @returns A Promise of the result
833
+ */
834
+ then(onfulfilled, onrejected) {
835
+ return this.promise.then(onfulfilled, onrejected);
836
+ }
837
+ /**
838
+ * Transforms the Ok value using a mapper function, leaving Err untouched.
839
+ *
840
+ * @template U - The type of the transformed value
841
+ * @param fn - Function to transform the Ok value
842
+ * @returns A new AsyncResult with the transformed value
843
+ *
844
+ * @example
845
+ * ```typescript
846
+ * const result = fetchUser("123")
847
+ * .map(user => user.name)
848
+ * .map(name => name.toUpperCase());
849
+ * ```
850
+ */
851
+ map(fn) {
852
+ return new AsyncResult(this.promise.then((result) => result.map(fn)));
853
+ }
854
+ /**
855
+ * Transforms the Err value using a mapper function, leaving Ok untouched.
856
+ *
857
+ * @template F - The type of the transformed error
858
+ * @param fn - Function to transform the Err value
859
+ * @returns A new AsyncResult with the transformed error
860
+ *
861
+ * @example
862
+ * ```typescript
863
+ * const result = fetchUser("123")
864
+ * .mapErr(e => new NetworkError({ cause: e }));
865
+ * ```
866
+ */
867
+ mapErr(fn) {
868
+ return new AsyncResult(this.promise.then((result) => result.mapErr(fn)));
869
+ }
870
+ /**
871
+ * Chains operations that return Results.
872
+ *
873
+ * @template R - The Result type returned by the function
874
+ * @param fn - Function that takes the Ok value and returns a Result, AsyncResult, or Promise
875
+ * @returns A new AsyncResult from the chained operation
876
+ *
877
+ * @example
878
+ * ```typescript
879
+ * const result = fetchUser("123")
880
+ * .andThen(user => fetchPermissions(user.id));
881
+ * ```
882
+ */
883
+ andThen(fn) {
884
+ return new AsyncResult(this.promise.then(async (result) => {
885
+ const resultValue = result._unsafeValue();
886
+ if (isErrValue(resultValue)) {
887
+ return Result.err(resultValue.error);
888
+ }
889
+ const nextResult = fn(resultValue.value);
890
+ if (nextResult instanceof AsyncResult) {
891
+ return await nextResult;
892
+ }
893
+ return await nextResult;
894
+ }));
895
+ }
896
+ /**
897
+ * Extracts the value from Ok or returns the Err for early returns.
898
+ *
899
+ * This is the async version of Result.take(). It returns a Promise that
900
+ * resolves to either the unwrapped value T or an Err Result.
901
+ *
902
+ * @returns Promise of the unwrapped value or Err Result
903
+ *
904
+ * @example
905
+ * ```typescript
906
+ * async function processUser(id: string): Promise<Result<string, AppError>> {
907
+ * const user = await fetchUser(id).take();
908
+ * if (isErr(user)) return user;
909
+ *
910
+ * const perms = await fetchPermissions(user.id).take();
911
+ * if (isErr(perms)) return perms;
912
+ *
913
+ * return Result.ok(perms.join(", "));
914
+ * }
915
+ * ```
916
+ */
917
+ async take() {
918
+ const result = await this.promise;
919
+ return result.take();
920
+ }
921
+ /**
922
+ * Returns the Ok value or throws the Err error.
923
+ *
924
+ * Async version of `Result.orThrow()`.
925
+ *
926
+ * @returns Promise of the unwrapped Ok value
927
+ * @throws The contained Err error
928
+ */
929
+ async orThrow() {
930
+ const result = await this.promise;
931
+ return result.orThrow();
932
+ }
933
+ /**
934
+ * Adds context to an Err result for early returns.
935
+ * Async version - can be chained before take().
936
+ *
937
+ * @param message - Context message describing the operation that failed
938
+ * @param extra - Optional additional context data
939
+ * @returns AsyncResult with context added to any error
940
+ *
941
+ * @example
942
+ * ```typescript
943
+ * const user = await fetchUser(id).context("failed to fetch user").take();
944
+ * if (isErr(user)) return user;
945
+ * ```
946
+ */
947
+ context(message, extra) {
948
+ return new AsyncResult(this.promise.then((result) => {
949
+ result.context(message, extra);
950
+ return result;
951
+ }));
952
+ }
953
+ /**
954
+ * Pattern matching for async Results.
955
+ *
956
+ * @template U - The type of the return value
957
+ * @param pattern - Object with ok and err handler functions
958
+ * @returns Promise of the result from calling either handler
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * const message = await fetchUser("123").match({
963
+ * ok: (user) => `Welcome, ${user.name}`,
964
+ * err: (error) => `Error: ${error.message}`
965
+ * });
966
+ * ```
967
+ */
968
+ async match(pattern) {
969
+ const result = await this.promise;
970
+ return result.match(pattern);
971
+ }
972
+ /**
973
+ * Returns the Ok value or a default value if Err.
974
+ *
975
+ * @template U - The type of the default value
976
+ * @param defaultValue - The value to return if the result is Err
977
+ * @returns Promise of the Ok value or the default value
978
+ */
979
+ async unwrapOr(defaultValue) {
980
+ const result = await this.promise;
981
+ return result.unwrapOr(defaultValue);
982
+ }
983
+ /**
984
+ * Returns the Ok value or computes a default from the error.
985
+ *
986
+ * @template U - The type of the default value
987
+ * @param fn - Function to compute the default value from the error
988
+ * @returns Promise of the Ok value or the computed default value
989
+ */
990
+ async unwrapOrElse(fn) {
991
+ const result = await this.promise;
992
+ return result.unwrapOrElse(fn);
993
+ }
994
+ /**
995
+ * Returns this result if Ok, otherwise returns the fallback.
996
+ *
997
+ * @template U - The type of the fallback Ok value
998
+ * @param other - The fallback Result or AsyncResult
999
+ * @returns AsyncResult of this or the fallback
1000
+ */
1001
+ or(other) {
1002
+ return new AsyncResult(this.promise.then(async (result) => {
1003
+ if (result.isOk()) {
1004
+ return result;
1005
+ }
1006
+ if (other instanceof AsyncResult) {
1007
+ return (await other);
1008
+ }
1009
+ return (await other);
1010
+ }));
1011
+ }
1012
+ /**
1013
+ * Returns this result if Ok, otherwise computes a fallback from the error.
1014
+ *
1015
+ * @template R - The Result or AsyncResult type returned by the fallback function
1016
+ * @param fn - Function to compute a fallback Result from the error
1017
+ * @returns AsyncResult of this or the computed fallback
1018
+ */
1019
+ orElse(fn) {
1020
+ return new AsyncResult(this.promise.then(async (result) => {
1021
+ const resultValue = result._unsafeValue();
1022
+ if (isOkValue(resultValue)) {
1023
+ return Result.ok(resultValue.value);
1024
+ }
1025
+ const nextResult = fn(resultValue.error);
1026
+ if (nextResult instanceof AsyncResult) {
1027
+ return await nextResult;
1028
+ }
1029
+ return await nextResult;
1030
+ }));
1031
+ }
1032
+ /**
1033
+ * Performs a side effect on the Ok value without changing the result.
1034
+ *
1035
+ * @param fn - Function to call with the Ok value (if Ok)
1036
+ * @returns This AsyncResult, unchanged
1037
+ */
1038
+ inspect(fn) {
1039
+ return new AsyncResult(this.promise.then(async (result) => {
1040
+ const resultValue = result._unsafeValue();
1041
+ if (isOkValue(resultValue)) {
1042
+ await fn(resultValue.value);
1043
+ }
1044
+ return result;
1045
+ }));
1046
+ }
1047
+ /**
1048
+ * Performs a side effect on the Err value without changing the result.
1049
+ *
1050
+ * @param fn - Function to call with the error value (if Err)
1051
+ * @returns This AsyncResult, unchanged
1052
+ */
1053
+ inspectErr(fn) {
1054
+ return new AsyncResult(this.promise.then(async (result) => {
1055
+ const resultValue = result._unsafeValue();
1056
+ if (isErrValue(resultValue)) {
1057
+ await fn(resultValue.error);
1058
+ }
1059
+ return result;
1060
+ }));
1061
+ }
1062
+ }