unthrown 3.0.1 → 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 +194 -36
- 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 +194 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -5
- 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
|
}
|
|
@@ -489,8 +517,12 @@ var AsyncRes = class AsyncRes {
|
|
|
489
517
|
* @example
|
|
490
518
|
* ```ts
|
|
491
519
|
* import { Ok } from "unthrown";
|
|
492
|
-
*
|
|
520
|
+
*
|
|
521
|
+
* Ok(2).map((n) => n + 1); // => Ok(3)
|
|
522
|
+
* Ok(42).unwrap(); // => 42
|
|
493
523
|
* ```
|
|
524
|
+
*
|
|
525
|
+
* @category Constructors
|
|
494
526
|
*/
|
|
495
527
|
function Ok(value) {
|
|
496
528
|
return okRes(value);
|
|
@@ -504,8 +536,12 @@ function Ok(value) {
|
|
|
504
536
|
* @example
|
|
505
537
|
* ```ts
|
|
506
538
|
* import { Err } from "unthrown";
|
|
507
|
-
*
|
|
539
|
+
*
|
|
540
|
+
* Err("not_found").map((n) => n + 1); // => Err("not_found") (map skipped)
|
|
541
|
+
* Err("not_found").unwrapErr(); // => "not_found"
|
|
508
542
|
* ```
|
|
543
|
+
*
|
|
544
|
+
* @category Constructors
|
|
509
545
|
*/
|
|
510
546
|
function Err(error) {
|
|
511
547
|
return errRes(error);
|
|
@@ -517,10 +553,16 @@ function Err(error) {
|
|
|
517
553
|
*
|
|
518
554
|
* @example
|
|
519
555
|
* ```ts
|
|
520
|
-
* 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
|
+
*
|
|
521
561
|
* declare const r: Result<number, string>;
|
|
522
562
|
* if (isOk(r)) r.value; // number, narrowed
|
|
523
563
|
* ```
|
|
564
|
+
*
|
|
565
|
+
* @category Guards
|
|
524
566
|
*/
|
|
525
567
|
function isOk(r) {
|
|
526
568
|
return r.tag === "Ok";
|
|
@@ -529,6 +571,19 @@ function isOk(r) {
|
|
|
529
571
|
* Type guard: narrow a {@link Result} to its `Err` variant, exposing `.error`.
|
|
530
572
|
*
|
|
531
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
|
|
532
587
|
*/
|
|
533
588
|
function isErr(r) {
|
|
534
589
|
return r.tag === "Err";
|
|
@@ -536,7 +591,27 @@ function isErr(r) {
|
|
|
536
591
|
/**
|
|
537
592
|
* Type guard: narrow a {@link Result} to its `Defect` variant, exposing `.cause`.
|
|
538
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
|
+
*
|
|
539
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
|
|
540
615
|
*/
|
|
541
616
|
function isDefect(r) {
|
|
542
617
|
return r.tag === "Defect";
|
|
@@ -564,6 +639,24 @@ function isDefect(r) {
|
|
|
564
639
|
* .map(({ user, org, label }) => render(user, org, label));
|
|
565
640
|
* // Result<View, NotFound>
|
|
566
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
|
|
567
660
|
*/
|
|
568
661
|
function Do() {
|
|
569
662
|
return Ok({});
|
|
@@ -613,10 +706,16 @@ function isDefectMarker(x) {
|
|
|
613
706
|
* @param value - the possibly-absent value.
|
|
614
707
|
* @param onAbsent - lazily produces the error for the absent case.
|
|
615
708
|
*
|
|
709
|
+
* @category Interop
|
|
710
|
+
*
|
|
616
711
|
* @example
|
|
617
712
|
* ```ts
|
|
618
713
|
* import { fromNullable } from "unthrown";
|
|
619
|
-
*
|
|
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)
|
|
620
719
|
* ```
|
|
621
720
|
*/
|
|
622
721
|
function fromNullable(value, onAbsent) {
|
|
@@ -647,11 +746,21 @@ function fromNullable(value, onAbsent) {
|
|
|
647
746
|
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
648
747
|
* @returns a function with the same arguments returning `Result<T, E>`.
|
|
649
748
|
*
|
|
749
|
+
* @category Interop
|
|
750
|
+
*
|
|
650
751
|
* @example
|
|
651
752
|
* ```ts
|
|
652
753
|
* import { fromThrowable } from "unthrown";
|
|
653
|
-
*
|
|
654
|
-
* 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")
|
|
655
764
|
* ```
|
|
656
765
|
*/
|
|
657
766
|
function fromThrowable(fn, qualify) {
|
|
@@ -686,17 +795,24 @@ function fromThrowable(fn, qualify) {
|
|
|
686
795
|
* @param qualify - triages a rejection `cause` into a modeled `E`, or marks it
|
|
687
796
|
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
688
797
|
*
|
|
798
|
+
* @category Interop
|
|
799
|
+
*
|
|
689
800
|
* @example
|
|
690
801
|
* ```ts
|
|
691
802
|
* import { fromPromise } from "unthrown";
|
|
803
|
+
*
|
|
804
|
+
* // A rejection with a NotFoundError becomes a modeled `Err`; anything else a Defect.
|
|
692
805
|
* const user = await fromPromise(fetchUser(id), (cause, defect) =>
|
|
693
806
|
* cause instanceof NotFoundError ? ("not_found" as const) : defect(cause),
|
|
694
807
|
* );
|
|
808
|
+
*
|
|
809
|
+
* user.unwrap(); // => the fetched user (on success)
|
|
810
|
+
* // when fetchUser rejects with NotFoundError: => Err("not_found")
|
|
695
811
|
* ```
|
|
696
812
|
*/
|
|
697
813
|
function fromPromise(promise, qualify) {
|
|
698
814
|
const triage = qualify;
|
|
699
|
-
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)));
|
|
700
816
|
}
|
|
701
817
|
/**
|
|
702
818
|
* Wrap a `Promise` asserted **not** to fail in any modeled way: any rejection
|
|
@@ -709,9 +825,20 @@ function fromPromise(promise, qualify) {
|
|
|
709
825
|
*
|
|
710
826
|
* @typeParam T - the resolved value type.
|
|
711
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
|
+
* ```
|
|
712
839
|
*/
|
|
713
840
|
function fromSafePromise(promise) {
|
|
714
|
-
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)));
|
|
715
842
|
}
|
|
716
843
|
function qualifyToResult(cause, qualify) {
|
|
717
844
|
try {
|
|
@@ -749,8 +876,10 @@ function foldRecord(results) {
|
|
|
749
876
|
let firstErr;
|
|
750
877
|
let firstDefect;
|
|
751
878
|
const values = {};
|
|
752
|
-
for (const [key, r] of Object.entries(results)) if (r.tag === "Defect")
|
|
753
|
-
|
|
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;
|
|
754
883
|
else Object.defineProperty(values, key, {
|
|
755
884
|
value: r.value,
|
|
756
885
|
enumerable: true,
|
|
@@ -771,11 +900,14 @@ function foldRecord(results) {
|
|
|
771
900
|
* collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
|
|
772
901
|
* use {@link allFromDict}.
|
|
773
902
|
*
|
|
903
|
+
* @category Aggregate
|
|
904
|
+
*
|
|
774
905
|
* @example
|
|
775
906
|
* ```ts
|
|
776
|
-
* import { all, Ok } from "unthrown";
|
|
777
|
-
*
|
|
778
|
-
* 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)
|
|
779
911
|
* ```
|
|
780
912
|
*/
|
|
781
913
|
function all(results) {
|
|
@@ -791,10 +923,14 @@ function all(results) {
|
|
|
791
923
|
* Same folding rules as {@link all}: first `Err` short-circuits, any `Defect`
|
|
792
924
|
* dominates. This is **not** error accumulation.
|
|
793
925
|
*
|
|
926
|
+
* @category Aggregate
|
|
927
|
+
*
|
|
794
928
|
* @example
|
|
795
929
|
* ```ts
|
|
796
|
-
* import { allFromDict, Ok } from "unthrown";
|
|
797
|
-
*
|
|
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")
|
|
798
934
|
* ```
|
|
799
935
|
*/
|
|
800
936
|
function allFromDict(results) {
|
|
@@ -810,10 +946,14 @@ function allFromDict(results) {
|
|
|
810
946
|
* short-circuits, any `Defect` dominates. As ever, the returned `AsyncResult`'s
|
|
811
947
|
* internal promise never rejects. For a **record**, use {@link allFromDictAsync}.
|
|
812
948
|
*
|
|
949
|
+
* @category Aggregate
|
|
950
|
+
*
|
|
813
951
|
* @example
|
|
814
952
|
* ```ts
|
|
815
953
|
* import { allAsync, fromSafePromise } from "unthrown";
|
|
816
|
-
*
|
|
954
|
+
*
|
|
955
|
+
* const both = allAsync([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
|
|
956
|
+
* (await both).unwrap(); // => [1, 2]
|
|
817
957
|
* ```
|
|
818
958
|
*/
|
|
819
959
|
function allAsync(results) {
|
|
@@ -827,10 +967,17 @@ function allAsync(results) {
|
|
|
827
967
|
* Resolved concurrently (order preserved), folded with the {@link all} rules,
|
|
828
968
|
* and the internal promise never rejects.
|
|
829
969
|
*
|
|
970
|
+
* @category Aggregate
|
|
971
|
+
*
|
|
830
972
|
* @example
|
|
831
973
|
* ```ts
|
|
832
974
|
* import { allFromDictAsync, fromSafePromise } from "unthrown";
|
|
833
|
-
*
|
|
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" }
|
|
834
981
|
* ```
|
|
835
982
|
*/
|
|
836
983
|
function allFromDictAsync(results) {
|
|
@@ -862,10 +1009,12 @@ function allFromDictAsync(results) {
|
|
|
862
1009
|
* (`AsyncResult.fromPromise`, `AsyncResult.all`, …), grouped by what they
|
|
863
1010
|
* return — a static lives in exactly one namespace.
|
|
864
1011
|
*
|
|
1012
|
+
* @category Facade
|
|
1013
|
+
*
|
|
865
1014
|
* @example
|
|
866
1015
|
* ```ts
|
|
867
1016
|
* import { Result } from "unthrown";
|
|
868
|
-
* Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // 2
|
|
1017
|
+
* Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // => 2
|
|
869
1018
|
* ```
|
|
870
1019
|
*/
|
|
871
1020
|
const Result = {
|
|
@@ -896,10 +1045,13 @@ const Result = {
|
|
|
896
1045
|
* {@link Result}, the free functions remain the primary, tree-shakeable API; the
|
|
897
1046
|
* value `AsyncResult` and the type {@link AsyncResult} share one name.
|
|
898
1047
|
*
|
|
1048
|
+
* @category Facade
|
|
1049
|
+
*
|
|
899
1050
|
* @example
|
|
900
1051
|
* ```ts
|
|
901
1052
|
* import { AsyncResult } from "unthrown";
|
|
902
1053
|
* const user = await AsyncResult.fromPromise(fetchUser(id), (c, defect) => defect(c));
|
|
1054
|
+
* user.unwrap(); // => the fetched user (on success)
|
|
903
1055
|
* ```
|
|
904
1056
|
*/
|
|
905
1057
|
const AsyncResult = {
|
|
@@ -918,7 +1070,10 @@ const AsyncResult = {
|
|
|
918
1070
|
* Extend the returned class to declare a concrete error. Supply the payload with
|
|
919
1071
|
* an instantiation expression; omit it for a payload-less error. A `message`
|
|
920
1072
|
* field in the payload is forwarded to `Error`. The `_tag` always reflects
|
|
921
|
-
* `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`.
|
|
922
1077
|
*
|
|
923
1078
|
* `_tag` is the discriminant used by {@link matchTags}; `Error.name` is the
|
|
924
1079
|
* human-facing label in stack traces and logs. By default they coincide, but
|
|
@@ -941,13 +1096,15 @@ const AsyncResult = {
|
|
|
941
1096
|
* @param options - optional overrides. `options.name` sets `Error.name`
|
|
942
1097
|
* independently of `tag` (defaults to `tag`).
|
|
943
1098
|
*
|
|
1099
|
+
* @category Tagged errors
|
|
1100
|
+
*
|
|
944
1101
|
* @example
|
|
945
1102
|
* ```ts
|
|
946
1103
|
* class NotFound extends TaggedError("NotFound") {}
|
|
947
1104
|
* class HttpError extends TaggedError("HttpError")<{ status: number }> {}
|
|
948
1105
|
*
|
|
949
|
-
* new NotFound()._tag; // "NotFound"
|
|
950
|
-
* new HttpError({ status: 500 }).status; // 500
|
|
1106
|
+
* new NotFound()._tag; // => "NotFound"
|
|
1107
|
+
* new HttpError({ status: 500 }).status; // => 500
|
|
951
1108
|
* ```
|
|
952
1109
|
*/
|
|
953
1110
|
function TaggedError(tag, options) {
|
|
@@ -966,8 +1123,9 @@ function TaggedError(tag, options) {
|
|
|
966
1123
|
}
|
|
967
1124
|
function matchTags(result, handlers) {
|
|
968
1125
|
const onErr = (error) => {
|
|
969
|
-
const
|
|
970
|
-
|
|
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);
|
|
971
1129
|
};
|
|
972
1130
|
return result.match({
|
|
973
1131
|
ok: handlers.Ok,
|