unthrown 3.0.0 → 3.1.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/README.md +5 -2
- package/dist/index.cjs +199 -43
- package/dist/index.d.cts +506 -107
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +506 -107
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +199 -43
- package/dist/index.mjs.map +1 -1
- package/package.json +6 -6
- package/docs/index.md +0 -1407
package/README.md
CHANGED
|
@@ -11,7 +11,10 @@ pnpm add unthrown
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import {
|
|
14
|
+
import { fromPromise, TaggedError } from "unthrown";
|
|
15
|
+
|
|
16
|
+
class NotFound extends TaggedError("NotFound") {} // our modeled domain failure
|
|
17
|
+
class NotFoundError extends Error {} // what `fetchUser` rejects with on a 404
|
|
15
18
|
|
|
16
19
|
const user = fromPromise(fetchUser(id), (cause, defect) =>
|
|
17
20
|
cause instanceof NotFoundError ? new NotFound() : defect(cause),
|
|
@@ -37,4 +40,4 @@ and complete API.
|
|
|
37
40
|
|
|
38
41
|
## License
|
|
39
42
|
|
|
40
|
-
[MIT](
|
|
43
|
+
[MIT](https://github.com/btravstack/unthrown/blob/main/LICENSE) © Benoit TRAVERS
|
package/dist/index.cjs
CHANGED
|
@@ -15,6 +15,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
|
15
15
|
* re-thrown (with its original stack) instead.
|
|
16
16
|
*
|
|
17
17
|
* @typeParam E - the type of the {@link UnwrapError.error} it carries.
|
|
18
|
+
*
|
|
19
|
+
* @category Errors
|
|
18
20
|
*/
|
|
19
21
|
var UnwrapError = class extends Error {
|
|
20
22
|
/**
|
|
@@ -129,7 +131,7 @@ var Res = class {
|
|
|
129
131
|
f(this.error);
|
|
130
132
|
return this;
|
|
131
133
|
} catch (cause) {
|
|
132
|
-
return
|
|
134
|
+
return observerThrowToDefect(cause, this.error);
|
|
133
135
|
}
|
|
134
136
|
}
|
|
135
137
|
flatTapErr(f) {
|
|
@@ -138,7 +140,7 @@ var Res = class {
|
|
|
138
140
|
const r = f(this.error);
|
|
139
141
|
return r.tag === "Ok" ? this : passThrough(r);
|
|
140
142
|
} catch (cause) {
|
|
141
|
-
return
|
|
143
|
+
return observerThrowToDefect(cause, this.error);
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
recoverDefect(f) {
|
|
@@ -155,7 +157,7 @@ var Res = class {
|
|
|
155
157
|
f(this.cause);
|
|
156
158
|
return this;
|
|
157
159
|
} catch (cause) {
|
|
158
|
-
return
|
|
160
|
+
return observerThrowToDefect(cause, this.cause);
|
|
159
161
|
}
|
|
160
162
|
}
|
|
161
163
|
match(cases) {
|
|
@@ -218,10 +220,10 @@ const RESULT_PROTO = Res.prototype;
|
|
|
218
220
|
* @internal
|
|
219
221
|
*/
|
|
220
222
|
function okRes(value) {
|
|
221
|
-
return Object.assign(Object.create(RESULT_PROTO), {
|
|
223
|
+
return Object.freeze(Object.assign(Object.create(RESULT_PROTO), {
|
|
222
224
|
tag: "Ok",
|
|
223
225
|
value
|
|
224
|
-
});
|
|
226
|
+
}));
|
|
225
227
|
}
|
|
226
228
|
/**
|
|
227
229
|
* Construct an `Err` result.
|
|
@@ -229,10 +231,10 @@ function okRes(value) {
|
|
|
229
231
|
* @internal
|
|
230
232
|
*/
|
|
231
233
|
function errRes(error) {
|
|
232
|
-
return Object.assign(Object.create(RESULT_PROTO), {
|
|
234
|
+
return Object.freeze(Object.assign(Object.create(RESULT_PROTO), {
|
|
233
235
|
tag: "Err",
|
|
234
236
|
error
|
|
235
|
-
});
|
|
237
|
+
}));
|
|
236
238
|
}
|
|
237
239
|
/**
|
|
238
240
|
* Construct a `Defect` result.
|
|
@@ -240,10 +242,10 @@ function errRes(error) {
|
|
|
240
242
|
* @internal
|
|
241
243
|
*/
|
|
242
244
|
function defectRes(cause) {
|
|
243
|
-
return Object.assign(Object.create(RESULT_PROTO), {
|
|
245
|
+
return Object.freeze(Object.assign(Object.create(RESULT_PROTO), {
|
|
244
246
|
tag: "Defect",
|
|
245
247
|
cause
|
|
246
|
-
});
|
|
248
|
+
}));
|
|
247
249
|
}
|
|
248
250
|
/**
|
|
249
251
|
* Type guard: is `x` a {@link Result} (any of `Ok` / `Err` / `Defect`)?
|
|
@@ -256,6 +258,20 @@ function defectRes(cause) {
|
|
|
256
258
|
* is not a `Result` and returns `false`.
|
|
257
259
|
*
|
|
258
260
|
* @returns `true` when `x` is a `Result` produced by this library.
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```ts
|
|
264
|
+
* import { isResult, Ok } from "unthrown";
|
|
265
|
+
*
|
|
266
|
+
* isResult(Ok(1)); // => true
|
|
267
|
+
* isResult({ tag: "Ok" }); // => false (look-alike, wrong prototype)
|
|
268
|
+
* isResult(Ok(1).toAsync()); // => false (an AsyncResult is not a Result)
|
|
269
|
+
*
|
|
270
|
+
* const x: unknown = Ok(1);
|
|
271
|
+
* if (isResult(x)) x.match({ ok: () => 1, err: () => 0, defect: () => -1 });
|
|
272
|
+
* ```
|
|
273
|
+
*
|
|
274
|
+
* @category Guards
|
|
259
275
|
*/
|
|
260
276
|
function isResult(x) {
|
|
261
277
|
return x instanceof Res;
|
|
@@ -274,6 +290,18 @@ function passThrough(self) {
|
|
|
274
290
|
return self;
|
|
275
291
|
}
|
|
276
292
|
/**
|
|
293
|
+
* A throw inside a *failure observer* (`tapErr` / `tapDefect` / `flatTapErr`)
|
|
294
|
+
* must not destroy the failure being observed — that is the exact place (e.g. a
|
|
295
|
+
* failing error-logger) where losing the underlying failure hurts most. The
|
|
296
|
+
* resulting Defect aggregates both: `errors[0]` is the observer's throw,
|
|
297
|
+
* `errors[1]` the original failure.
|
|
298
|
+
*
|
|
299
|
+
* @internal
|
|
300
|
+
*/
|
|
301
|
+
function observerThrowToDefect(thrown, original) {
|
|
302
|
+
return defectRes(new AggregateError([thrown, original], "unthrown: a failure-observer callback threw; errors[0] is the callback's throw, errors[1] the original failure"));
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
277
305
|
* Validate that a `bind`/`let` scope is a real (non-null) object before merging a
|
|
278
306
|
* key into it.
|
|
279
307
|
*
|
|
@@ -292,7 +320,7 @@ function passThrough(self) {
|
|
|
292
320
|
* @internal
|
|
293
321
|
*/
|
|
294
322
|
function scopeOf(value) {
|
|
295
|
-
if (typeof value !== "object" || value === null) throw new TypeError("bind/let requires an object scope — start a do-chain with Do()");
|
|
323
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new TypeError("bind/let requires an object scope — start a do-chain with Do()");
|
|
296
324
|
return value;
|
|
297
325
|
}
|
|
298
326
|
/**
|
|
@@ -420,7 +448,7 @@ var AsyncRes = class AsyncRes {
|
|
|
420
448
|
f(r.error);
|
|
421
449
|
return r;
|
|
422
450
|
} catch (cause) {
|
|
423
|
-
return
|
|
451
|
+
return observerThrowToDefect(cause, r.error);
|
|
424
452
|
}
|
|
425
453
|
}));
|
|
426
454
|
}
|
|
@@ -431,7 +459,7 @@ var AsyncRes = class AsyncRes {
|
|
|
431
459
|
const inner = await f(r.error);
|
|
432
460
|
return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
|
|
433
461
|
} catch (cause) {
|
|
434
|
-
return
|
|
462
|
+
return observerThrowToDefect(cause, r.error);
|
|
435
463
|
}
|
|
436
464
|
}));
|
|
437
465
|
}
|
|
@@ -452,7 +480,7 @@ var AsyncRes = class AsyncRes {
|
|
|
452
480
|
f(r.cause);
|
|
453
481
|
return r;
|
|
454
482
|
} catch (cause) {
|
|
455
|
-
return
|
|
483
|
+
return observerThrowToDefect(cause, r.cause);
|
|
456
484
|
}
|
|
457
485
|
}));
|
|
458
486
|
}
|
|
@@ -469,11 +497,7 @@ var AsyncRes = class AsyncRes {
|
|
|
469
497
|
return this.promise.then((r) => r.unwrapOr(fallback));
|
|
470
498
|
}
|
|
471
499
|
unwrapOrElse(f) {
|
|
472
|
-
return this.promise.then((r) =>
|
|
473
|
-
if (r.tag === "Ok") return r.value;
|
|
474
|
-
if (r.tag === "Defect") throw r.cause;
|
|
475
|
-
return f(r.error);
|
|
476
|
-
});
|
|
500
|
+
return this.promise.then((r) => r.unwrapOrElse(f));
|
|
477
501
|
}
|
|
478
502
|
getOrNull() {
|
|
479
503
|
return this.promise.then((r) => r.getOrNull());
|
|
@@ -493,8 +517,12 @@ var AsyncRes = class AsyncRes {
|
|
|
493
517
|
* @example
|
|
494
518
|
* ```ts
|
|
495
519
|
* import { Ok } from "unthrown";
|
|
496
|
-
*
|
|
520
|
+
*
|
|
521
|
+
* Ok(2).map((n) => n + 1); // => Ok(3)
|
|
522
|
+
* Ok(42).unwrap(); // => 42
|
|
497
523
|
* ```
|
|
524
|
+
*
|
|
525
|
+
* @category Constructors
|
|
498
526
|
*/
|
|
499
527
|
function Ok(value) {
|
|
500
528
|
return okRes(value);
|
|
@@ -508,8 +536,12 @@ function Ok(value) {
|
|
|
508
536
|
* @example
|
|
509
537
|
* ```ts
|
|
510
538
|
* import { Err } from "unthrown";
|
|
511
|
-
*
|
|
539
|
+
*
|
|
540
|
+
* Err("not_found").map((n) => n + 1); // => Err("not_found") (map skipped)
|
|
541
|
+
* Err("not_found").unwrapErr(); // => "not_found"
|
|
512
542
|
* ```
|
|
543
|
+
*
|
|
544
|
+
* @category Constructors
|
|
513
545
|
*/
|
|
514
546
|
function Err(error) {
|
|
515
547
|
return errRes(error);
|
|
@@ -521,10 +553,16 @@ function Err(error) {
|
|
|
521
553
|
*
|
|
522
554
|
* @example
|
|
523
555
|
* ```ts
|
|
524
|
-
* import { isOk, type Result } from "unthrown";
|
|
556
|
+
* import { isOk, Ok, Err, type Result } from "unthrown";
|
|
557
|
+
*
|
|
558
|
+
* isOk(Ok(1)); // => true
|
|
559
|
+
* isOk(Err("boom")); // => false
|
|
560
|
+
*
|
|
525
561
|
* declare const r: Result<number, string>;
|
|
526
562
|
* if (isOk(r)) r.value; // number, narrowed
|
|
527
563
|
* ```
|
|
564
|
+
*
|
|
565
|
+
* @category Guards
|
|
528
566
|
*/
|
|
529
567
|
function isOk(r) {
|
|
530
568
|
return r.tag === "Ok";
|
|
@@ -533,6 +571,19 @@ function isOk(r) {
|
|
|
533
571
|
* Type guard: narrow a {@link Result} to its `Err` variant, exposing `.error`.
|
|
534
572
|
*
|
|
535
573
|
* @returns `true` when `r` is `Err`.
|
|
574
|
+
*
|
|
575
|
+
* @example
|
|
576
|
+
* ```ts
|
|
577
|
+
* import { isErr, Ok, Err, type Result } from "unthrown";
|
|
578
|
+
*
|
|
579
|
+
* isErr(Err("boom")); // => true
|
|
580
|
+
* isErr(Ok(1)); // => false
|
|
581
|
+
*
|
|
582
|
+
* declare const r: Result<number, string>;
|
|
583
|
+
* if (isErr(r)) r.error; // string, narrowed
|
|
584
|
+
* ```
|
|
585
|
+
*
|
|
586
|
+
* @category Guards
|
|
536
587
|
*/
|
|
537
588
|
function isErr(r) {
|
|
538
589
|
return r.tag === "Err";
|
|
@@ -540,7 +591,27 @@ function isErr(r) {
|
|
|
540
591
|
/**
|
|
541
592
|
* Type guard: narrow a {@link Result} to its `Defect` variant, exposing `.cause`.
|
|
542
593
|
*
|
|
594
|
+
* @remarks
|
|
595
|
+
* A `Defect` has no public constructor — it only arises at a boundary (e.g. a
|
|
596
|
+
* callback throwing inside a combinator). This guard is how you detect one.
|
|
597
|
+
*
|
|
543
598
|
* @returns `true` when `r` is a `Defect`.
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```ts
|
|
602
|
+
* import { isDefect, Ok } from "unthrown";
|
|
603
|
+
*
|
|
604
|
+
* // A throw inside a combinator is captured as a Defect:
|
|
605
|
+
* const r = Ok(1).map(() => {
|
|
606
|
+
* throw new Error("boom");
|
|
607
|
+
* });
|
|
608
|
+
* isDefect(r); // => true
|
|
609
|
+
* isDefect(Ok(1)); // => false
|
|
610
|
+
*
|
|
611
|
+
* if (isDefect(r)) r.cause; // unknown, narrowed
|
|
612
|
+
* ```
|
|
613
|
+
*
|
|
614
|
+
* @category Guards
|
|
544
615
|
*/
|
|
545
616
|
function isDefect(r) {
|
|
546
617
|
return r.tag === "Defect";
|
|
@@ -568,6 +639,24 @@ function isDefect(r) {
|
|
|
568
639
|
* .map(({ user, org, label }) => render(user, org, label));
|
|
569
640
|
* // Result<View, NotFound>
|
|
570
641
|
* ```
|
|
642
|
+
*
|
|
643
|
+
* @example
|
|
644
|
+
* ```ts
|
|
645
|
+
* import { Do, Ok, Err } from "unthrown";
|
|
646
|
+
*
|
|
647
|
+
* // Ok path — the scope accumulates:
|
|
648
|
+
* Do()
|
|
649
|
+
* .bind("a", () => Ok(2))
|
|
650
|
+
* .let("b", ({ a }) => a * 10)
|
|
651
|
+
* .map(({ a, b }) => a + b); // => Ok(22)
|
|
652
|
+
*
|
|
653
|
+
* // Err path — the first Err short-circuits the rest:
|
|
654
|
+
* Do()
|
|
655
|
+
* .bind("a", () => Err("boom"))
|
|
656
|
+
* .let("b", ({ a }) => a); // => Err("boom")
|
|
657
|
+
* ```
|
|
658
|
+
*
|
|
659
|
+
* @category Do-notation
|
|
571
660
|
*/
|
|
572
661
|
function Do() {
|
|
573
662
|
return Ok({});
|
|
@@ -617,10 +706,16 @@ function isDefectMarker(x) {
|
|
|
617
706
|
* @param value - the possibly-absent value.
|
|
618
707
|
* @param onAbsent - lazily produces the error for the absent case.
|
|
619
708
|
*
|
|
709
|
+
* @category Interop
|
|
710
|
+
*
|
|
620
711
|
* @example
|
|
621
712
|
* ```ts
|
|
622
713
|
* import { fromNullable } from "unthrown";
|
|
623
|
-
*
|
|
714
|
+
*
|
|
715
|
+
* const map = new Map([["a", 1]]);
|
|
716
|
+
* fromNullable(map.get("a"), () => "absent").unwrap(); // => 1
|
|
717
|
+
* fromNullable(map.get("z"), () => "absent"); // => Err("absent")
|
|
718
|
+
* fromNullable(0, () => "absent").unwrap(); // => 0 (falsy but present)
|
|
624
719
|
* ```
|
|
625
720
|
*/
|
|
626
721
|
function fromNullable(value, onAbsent) {
|
|
@@ -651,11 +746,21 @@ function fromNullable(value, onAbsent) {
|
|
|
651
746
|
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
652
747
|
* @returns a function with the same arguments returning `Result<T, E>`.
|
|
653
748
|
*
|
|
749
|
+
* @category Interop
|
|
750
|
+
*
|
|
654
751
|
* @example
|
|
655
752
|
* ```ts
|
|
656
753
|
* import { fromThrowable } from "unthrown";
|
|
657
|
-
*
|
|
658
|
-
* parse
|
|
754
|
+
*
|
|
755
|
+
* // Model the parse failure as an `Err`, everything unexpected as a `Defect`.
|
|
756
|
+
* const parse = fromThrowable(
|
|
757
|
+
* (text: string) => JSON.parse(text) as unknown,
|
|
758
|
+
* (cause, defect) =>
|
|
759
|
+
* cause instanceof SyntaxError ? ("invalid_json" as const) : defect(cause),
|
|
760
|
+
* );
|
|
761
|
+
*
|
|
762
|
+
* parse('{"ok":true}').unwrap(); // => { ok: true }
|
|
763
|
+
* parse("nope"); // => Err("invalid_json")
|
|
659
764
|
* ```
|
|
660
765
|
*/
|
|
661
766
|
function fromThrowable(fn, qualify) {
|
|
@@ -690,17 +795,24 @@ function fromThrowable(fn, qualify) {
|
|
|
690
795
|
* @param qualify - triages a rejection `cause` into a modeled `E`, or marks it
|
|
691
796
|
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
692
797
|
*
|
|
798
|
+
* @category Interop
|
|
799
|
+
*
|
|
693
800
|
* @example
|
|
694
801
|
* ```ts
|
|
695
802
|
* import { fromPromise } from "unthrown";
|
|
803
|
+
*
|
|
804
|
+
* // A rejection with a NotFoundError becomes a modeled `Err`; anything else a Defect.
|
|
696
805
|
* const user = await fromPromise(fetchUser(id), (cause, defect) =>
|
|
697
806
|
* cause instanceof NotFoundError ? ("not_found" as const) : defect(cause),
|
|
698
807
|
* );
|
|
808
|
+
*
|
|
809
|
+
* user.unwrap(); // => the fetched user (on success)
|
|
810
|
+
* // when fetchUser rejects with NotFoundError: => Err("not_found")
|
|
699
811
|
* ```
|
|
700
812
|
*/
|
|
701
813
|
function fromPromise(promise, qualify) {
|
|
702
814
|
const triage = qualify;
|
|
703
|
-
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : promise).then((value) => okRes(value), (cause) => qualifyToResult(cause, triage)));
|
|
815
|
+
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : Promise.resolve(promise)).then((value) => okRes(value), (cause) => qualifyToResult(cause, triage)));
|
|
704
816
|
}
|
|
705
817
|
/**
|
|
706
818
|
* Wrap a `Promise` asserted **not** to fail in any modeled way: any rejection
|
|
@@ -713,9 +825,20 @@ function fromPromise(promise, qualify) {
|
|
|
713
825
|
*
|
|
714
826
|
* @typeParam T - the resolved value type.
|
|
715
827
|
* @param promise - the promise, or a thunk returning one.
|
|
828
|
+
*
|
|
829
|
+
* @category Interop
|
|
830
|
+
*
|
|
831
|
+
* @example
|
|
832
|
+
* ```ts
|
|
833
|
+
* import { fromSafePromise } from "unthrown";
|
|
834
|
+
*
|
|
835
|
+
* (await fromSafePromise(Promise.resolve(3))).unwrap(); // => 3
|
|
836
|
+
* // a rejection becomes a Defect (never a modeled Err):
|
|
837
|
+
* await fromSafePromise(Promise.reject(new Error("boom"))); // => Defect(Error("boom"))
|
|
838
|
+
* ```
|
|
716
839
|
*/
|
|
717
840
|
function fromSafePromise(promise) {
|
|
718
|
-
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : promise).then((value) => okRes(value), (cause) => defectRes(cause)));
|
|
841
|
+
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : Promise.resolve(promise)).then((value) => okRes(value), (cause) => defectRes(cause)));
|
|
719
842
|
}
|
|
720
843
|
function qualifyToResult(cause, qualify) {
|
|
721
844
|
try {
|
|
@@ -735,8 +858,10 @@ function foldArray(results) {
|
|
|
735
858
|
let firstErr;
|
|
736
859
|
let firstDefect;
|
|
737
860
|
const values = [];
|
|
738
|
-
for (const r of results) if (r.tag === "Defect")
|
|
739
|
-
|
|
861
|
+
for (const r of results) if (r.tag === "Defect") {
|
|
862
|
+
firstDefect ??= r;
|
|
863
|
+
break;
|
|
864
|
+
} else if (r.tag === "Err") firstErr ??= r;
|
|
740
865
|
else values.push(r.value);
|
|
741
866
|
return firstDefect ?? firstErr ?? Ok(values);
|
|
742
867
|
}
|
|
@@ -751,8 +876,10 @@ function foldRecord(results) {
|
|
|
751
876
|
let firstErr;
|
|
752
877
|
let firstDefect;
|
|
753
878
|
const values = {};
|
|
754
|
-
for (const [key, r] of Object.entries(results)) if (r.tag === "Defect")
|
|
755
|
-
|
|
879
|
+
for (const [key, r] of Object.entries(results)) if (r.tag === "Defect") {
|
|
880
|
+
firstDefect ??= r;
|
|
881
|
+
break;
|
|
882
|
+
} else if (r.tag === "Err") firstErr ??= r;
|
|
756
883
|
else Object.defineProperty(values, key, {
|
|
757
884
|
value: r.value,
|
|
758
885
|
enumerable: true,
|
|
@@ -773,11 +900,14 @@ function foldRecord(results) {
|
|
|
773
900
|
* collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
|
|
774
901
|
* use {@link allFromDict}.
|
|
775
902
|
*
|
|
903
|
+
* @category Aggregate
|
|
904
|
+
*
|
|
776
905
|
* @example
|
|
777
906
|
* ```ts
|
|
778
|
-
* import { all, Ok } from "unthrown";
|
|
779
|
-
*
|
|
780
|
-
* all([Ok(1), Ok(
|
|
907
|
+
* import { all, Ok, Err } from "unthrown";
|
|
908
|
+
*
|
|
909
|
+
* all([Ok(1), Ok("a"), Ok(true)]).unwrap(); // => [1, "a", true] (typed [number, string, boolean])
|
|
910
|
+
* all([Ok(1), Err("e"), Ok(3)]); // => Err("e") (short-circuits on the first Err)
|
|
781
911
|
* ```
|
|
782
912
|
*/
|
|
783
913
|
function all(results) {
|
|
@@ -793,10 +923,14 @@ function all(results) {
|
|
|
793
923
|
* Same folding rules as {@link all}: first `Err` short-circuits, any `Defect`
|
|
794
924
|
* dominates. This is **not** error accumulation.
|
|
795
925
|
*
|
|
926
|
+
* @category Aggregate
|
|
927
|
+
*
|
|
796
928
|
* @example
|
|
797
929
|
* ```ts
|
|
798
|
-
* import { allFromDict, Ok } from "unthrown";
|
|
799
|
-
*
|
|
930
|
+
* import { allFromDict, Ok, Err } from "unthrown";
|
|
931
|
+
*
|
|
932
|
+
* allFromDict({ id: Ok(1), name: Ok("ada") }).unwrap(); // => { id: 1, name: "ada" }
|
|
933
|
+
* allFromDict({ id: Ok(1), name: Err("missing") }); // => Err("missing")
|
|
800
934
|
* ```
|
|
801
935
|
*/
|
|
802
936
|
function allFromDict(results) {
|
|
@@ -812,10 +946,14 @@ function allFromDict(results) {
|
|
|
812
946
|
* short-circuits, any `Defect` dominates. As ever, the returned `AsyncResult`'s
|
|
813
947
|
* internal promise never rejects. For a **record**, use {@link allFromDictAsync}.
|
|
814
948
|
*
|
|
949
|
+
* @category Aggregate
|
|
950
|
+
*
|
|
815
951
|
* @example
|
|
816
952
|
* ```ts
|
|
817
953
|
* import { allAsync, fromSafePromise } from "unthrown";
|
|
818
|
-
*
|
|
954
|
+
*
|
|
955
|
+
* const both = allAsync([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
|
|
956
|
+
* (await both).unwrap(); // => [1, 2]
|
|
819
957
|
* ```
|
|
820
958
|
*/
|
|
821
959
|
function allAsync(results) {
|
|
@@ -829,10 +967,17 @@ function allAsync(results) {
|
|
|
829
967
|
* Resolved concurrently (order preserved), folded with the {@link all} rules,
|
|
830
968
|
* and the internal promise never rejects.
|
|
831
969
|
*
|
|
970
|
+
* @category Aggregate
|
|
971
|
+
*
|
|
832
972
|
* @example
|
|
833
973
|
* ```ts
|
|
834
974
|
* import { allFromDictAsync, fromSafePromise } from "unthrown";
|
|
835
|
-
*
|
|
975
|
+
*
|
|
976
|
+
* const both = allFromDictAsync({
|
|
977
|
+
* a: fromSafePromise(Promise.resolve(1)),
|
|
978
|
+
* b: fromSafePromise(Promise.resolve("x")),
|
|
979
|
+
* });
|
|
980
|
+
* (await both).unwrap(); // => { a: 1, b: "x" }
|
|
836
981
|
* ```
|
|
837
982
|
*/
|
|
838
983
|
function allFromDictAsync(results) {
|
|
@@ -864,10 +1009,12 @@ function allFromDictAsync(results) {
|
|
|
864
1009
|
* (`AsyncResult.fromPromise`, `AsyncResult.all`, …), grouped by what they
|
|
865
1010
|
* return — a static lives in exactly one namespace.
|
|
866
1011
|
*
|
|
1012
|
+
* @category Facade
|
|
1013
|
+
*
|
|
867
1014
|
* @example
|
|
868
1015
|
* ```ts
|
|
869
1016
|
* import { Result } from "unthrown";
|
|
870
|
-
* Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // 2
|
|
1017
|
+
* Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // => 2
|
|
871
1018
|
* ```
|
|
872
1019
|
*/
|
|
873
1020
|
const Result = {
|
|
@@ -898,10 +1045,13 @@ const Result = {
|
|
|
898
1045
|
* {@link Result}, the free functions remain the primary, tree-shakeable API; the
|
|
899
1046
|
* value `AsyncResult` and the type {@link AsyncResult} share one name.
|
|
900
1047
|
*
|
|
1048
|
+
* @category Facade
|
|
1049
|
+
*
|
|
901
1050
|
* @example
|
|
902
1051
|
* ```ts
|
|
903
1052
|
* import { AsyncResult } from "unthrown";
|
|
904
1053
|
* const user = await AsyncResult.fromPromise(fetchUser(id), (c, defect) => defect(c));
|
|
1054
|
+
* user.unwrap(); // => the fetched user (on success)
|
|
905
1055
|
* ```
|
|
906
1056
|
*/
|
|
907
1057
|
const AsyncResult = {
|
|
@@ -920,7 +1070,10 @@ const AsyncResult = {
|
|
|
920
1070
|
* Extend the returned class to declare a concrete error. Supply the payload with
|
|
921
1071
|
* an instantiation expression; omit it for a payload-less error. A `message`
|
|
922
1072
|
* field in the payload is forwarded to `Error`. The `_tag` always reflects
|
|
923
|
-
* `tag` and cannot be overridden by the payload.
|
|
1073
|
+
* `tag` and cannot be overridden by the payload. `name` is likewise reserved —
|
|
1074
|
+
* it is the display label (set it with `options.name`); a payload `name` is
|
|
1075
|
+
* rejected at compile time (and excluded from the instance type), so it can't
|
|
1076
|
+
* shadow `Error.name`.
|
|
924
1077
|
*
|
|
925
1078
|
* `_tag` is the discriminant used by {@link matchTags}; `Error.name` is the
|
|
926
1079
|
* human-facing label in stack traces and logs. By default they coincide, but
|
|
@@ -943,13 +1096,15 @@ const AsyncResult = {
|
|
|
943
1096
|
* @param options - optional overrides. `options.name` sets `Error.name`
|
|
944
1097
|
* independently of `tag` (defaults to `tag`).
|
|
945
1098
|
*
|
|
1099
|
+
* @category Tagged errors
|
|
1100
|
+
*
|
|
946
1101
|
* @example
|
|
947
1102
|
* ```ts
|
|
948
1103
|
* class NotFound extends TaggedError("NotFound") {}
|
|
949
1104
|
* class HttpError extends TaggedError("HttpError")<{ status: number }> {}
|
|
950
1105
|
*
|
|
951
|
-
* new NotFound()._tag; // "NotFound"
|
|
952
|
-
* new HttpError({ status: 500 }).status; // 500
|
|
1106
|
+
* new NotFound()._tag; // => "NotFound"
|
|
1107
|
+
* new HttpError({ status: 500 }).status; // => 500
|
|
953
1108
|
* ```
|
|
954
1109
|
*/
|
|
955
1110
|
function TaggedError(tag, options) {
|
|
@@ -968,8 +1123,9 @@ function TaggedError(tag, options) {
|
|
|
968
1123
|
}
|
|
969
1124
|
function matchTags(result, handlers) {
|
|
970
1125
|
const onErr = (error) => {
|
|
971
|
-
const
|
|
972
|
-
|
|
1126
|
+
const tag = error._tag;
|
|
1127
|
+
const handler = tag === "Ok" || tag === "Defect" || !Object.hasOwn(handlers, tag) ? void 0 : handlers[tag];
|
|
1128
|
+
return handler ? handler(error) : handlers.Defect(error);
|
|
973
1129
|
};
|
|
974
1130
|
return result.match({
|
|
975
1131
|
ok: handlers.Ok,
|