@qlever-llc/result 0.8.4 → 0.9.0-rc.10

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