unthrown 3.0.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,10 @@ pnpm add unthrown
11
11
  ```
12
12
 
13
13
  ```ts
14
- import { Ok, Err, fromPromise, type Result } from "unthrown";
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](../../LICENSE) © Benoit TRAVERS
43
+ [MIT](https://github.com/btravstack/unthrown/blob/main/LICENSE) © Benoit TRAVERS
package/dist/index.cjs CHANGED
@@ -14,7 +14,14 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
14
14
  * A `Defect` is never wrapped in an `UnwrapError`: its original cause is
15
15
  * re-thrown (with its original stack) instead.
16
16
  *
17
+ * `unwrap()` and `unwrapErr()` are type-gated (`this: Result<T, never>` /
18
+ * `Result<never, E>`), so the wrong-variant branch that throws this is
19
+ * unreachable through well-typed code — it remains only as a defensive guard
20
+ * against unsound runtime misuse (e.g. an `as` cast past the gate).
21
+ *
17
22
  * @typeParam E - the type of the {@link UnwrapError.error} it carries.
23
+ *
24
+ * @category Errors
18
25
  */
19
26
  var UnwrapError = class extends Error {
20
27
  /**
@@ -129,7 +136,7 @@ var Res = class {
129
136
  f(this.error);
130
137
  return this;
131
138
  } catch (cause) {
132
- return defectRes(cause);
139
+ return observerThrowToDefect(cause, this.error);
133
140
  }
134
141
  }
135
142
  flatTapErr(f) {
@@ -138,7 +145,7 @@ var Res = class {
138
145
  const r = f(this.error);
139
146
  return r.tag === "Ok" ? this : passThrough(r);
140
147
  } catch (cause) {
141
- return defectRes(cause);
148
+ return observerThrowToDefect(cause, this.error);
142
149
  }
143
150
  }
144
151
  recoverDefect(f) {
@@ -155,7 +162,7 @@ var Res = class {
155
162
  f(this.cause);
156
163
  return this;
157
164
  } catch (cause) {
158
- return defectRes(cause);
165
+ return observerThrowToDefect(cause, this.cause);
159
166
  }
160
167
  }
161
168
  match(cases) {
@@ -218,10 +225,10 @@ const RESULT_PROTO = Res.prototype;
218
225
  * @internal
219
226
  */
220
227
  function okRes(value) {
221
- return Object.assign(Object.create(RESULT_PROTO), {
228
+ return Object.freeze(Object.assign(Object.create(RESULT_PROTO), {
222
229
  tag: "Ok",
223
230
  value
224
- });
231
+ }));
225
232
  }
226
233
  /**
227
234
  * Construct an `Err` result.
@@ -229,10 +236,10 @@ function okRes(value) {
229
236
  * @internal
230
237
  */
231
238
  function errRes(error) {
232
- return Object.assign(Object.create(RESULT_PROTO), {
239
+ return Object.freeze(Object.assign(Object.create(RESULT_PROTO), {
233
240
  tag: "Err",
234
241
  error
235
- });
242
+ }));
236
243
  }
237
244
  /**
238
245
  * Construct a `Defect` result.
@@ -240,10 +247,10 @@ function errRes(error) {
240
247
  * @internal
241
248
  */
242
249
  function defectRes(cause) {
243
- return Object.assign(Object.create(RESULT_PROTO), {
250
+ return Object.freeze(Object.assign(Object.create(RESULT_PROTO), {
244
251
  tag: "Defect",
245
252
  cause
246
- });
253
+ }));
247
254
  }
248
255
  /**
249
256
  * Type guard: is `x` a {@link Result} (any of `Ok` / `Err` / `Defect`)?
@@ -256,6 +263,20 @@ function defectRes(cause) {
256
263
  * is not a `Result` and returns `false`.
257
264
  *
258
265
  * @returns `true` when `x` is a `Result` produced by this library.
266
+ *
267
+ * @example
268
+ * ```ts
269
+ * import { isResult, Ok } from "unthrown";
270
+ *
271
+ * isResult(Ok(1)); // => true
272
+ * isResult({ tag: "Ok" }); // => false (look-alike, wrong prototype)
273
+ * isResult(Ok(1).toAsync()); // => false (an AsyncResult is not a Result)
274
+ *
275
+ * const x: unknown = Ok(1);
276
+ * if (isResult(x)) x.match({ ok: () => 1, err: () => 0, defect: () => -1 });
277
+ * ```
278
+ *
279
+ * @category Guards
259
280
  */
260
281
  function isResult(x) {
261
282
  return x instanceof Res;
@@ -274,6 +295,18 @@ function passThrough(self) {
274
295
  return self;
275
296
  }
276
297
  /**
298
+ * A throw inside a *failure observer* (`tapErr` / `tapDefect` / `flatTapErr`)
299
+ * must not destroy the failure being observed — that is the exact place (e.g. a
300
+ * failing error-logger) where losing the underlying failure hurts most. The
301
+ * resulting Defect aggregates both: `errors[0]` is the observer's throw,
302
+ * `errors[1]` the original failure.
303
+ *
304
+ * @internal
305
+ */
306
+ function observerThrowToDefect(thrown, original) {
307
+ return defectRes(new AggregateError([thrown, original], "unthrown: a failure-observer callback threw; errors[0] is the callback's throw, errors[1] the original failure"));
308
+ }
309
+ /**
277
310
  * Validate that a `bind`/`let` scope is a real (non-null) object before merging a
278
311
  * key into it.
279
312
  *
@@ -292,7 +325,7 @@ function passThrough(self) {
292
325
  * @internal
293
326
  */
294
327
  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()");
328
+ 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
329
  return value;
297
330
  }
298
331
  /**
@@ -420,7 +453,7 @@ var AsyncRes = class AsyncRes {
420
453
  f(r.error);
421
454
  return r;
422
455
  } catch (cause) {
423
- return defectRes(cause);
456
+ return observerThrowToDefect(cause, r.error);
424
457
  }
425
458
  }));
426
459
  }
@@ -431,7 +464,7 @@ var AsyncRes = class AsyncRes {
431
464
  const inner = await f(r.error);
432
465
  return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
433
466
  } catch (cause) {
434
- return defectRes(cause);
467
+ return observerThrowToDefect(cause, r.error);
435
468
  }
436
469
  }));
437
470
  }
@@ -452,7 +485,7 @@ var AsyncRes = class AsyncRes {
452
485
  f(r.cause);
453
486
  return r;
454
487
  } catch (cause) {
455
- return defectRes(cause);
488
+ return observerThrowToDefect(cause, r.cause);
456
489
  }
457
490
  }));
458
491
  }
@@ -489,8 +522,12 @@ var AsyncRes = class AsyncRes {
489
522
  * @example
490
523
  * ```ts
491
524
  * import { Ok } from "unthrown";
492
- * Ok(42).unwrap(); // 42
525
+ *
526
+ * Ok(2).map((n) => n + 1); // => Ok(3)
527
+ * Ok(42).unwrap(); // => 42
493
528
  * ```
529
+ *
530
+ * @category Constructors
494
531
  */
495
532
  function Ok(value) {
496
533
  return okRes(value);
@@ -504,8 +541,12 @@ function Ok(value) {
504
541
  * @example
505
542
  * ```ts
506
543
  * import { Err } from "unthrown";
507
- * Err("not_found").unwrapErr(); // "not_found"
544
+ *
545
+ * Err("not_found").map((n) => n + 1); // => Err("not_found") (map skipped)
546
+ * Err("not_found").unwrapErr(); // => "not_found"
508
547
  * ```
548
+ *
549
+ * @category Constructors
509
550
  */
510
551
  function Err(error) {
511
552
  return errRes(error);
@@ -517,10 +558,16 @@ function Err(error) {
517
558
  *
518
559
  * @example
519
560
  * ```ts
520
- * import { isOk, type Result } from "unthrown";
561
+ * import { isOk, Ok, Err, type Result } from "unthrown";
562
+ *
563
+ * isOk(Ok(1)); // => true
564
+ * isOk(Err("boom")); // => false
565
+ *
521
566
  * declare const r: Result<number, string>;
522
567
  * if (isOk(r)) r.value; // number, narrowed
523
568
  * ```
569
+ *
570
+ * @category Guards
524
571
  */
525
572
  function isOk(r) {
526
573
  return r.tag === "Ok";
@@ -529,6 +576,19 @@ function isOk(r) {
529
576
  * Type guard: narrow a {@link Result} to its `Err` variant, exposing `.error`.
530
577
  *
531
578
  * @returns `true` when `r` is `Err`.
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * import { isErr, Ok, Err, type Result } from "unthrown";
583
+ *
584
+ * isErr(Err("boom")); // => true
585
+ * isErr(Ok(1)); // => false
586
+ *
587
+ * declare const r: Result<number, string>;
588
+ * if (isErr(r)) r.error; // string, narrowed
589
+ * ```
590
+ *
591
+ * @category Guards
532
592
  */
533
593
  function isErr(r) {
534
594
  return r.tag === "Err";
@@ -536,7 +596,27 @@ function isErr(r) {
536
596
  /**
537
597
  * Type guard: narrow a {@link Result} to its `Defect` variant, exposing `.cause`.
538
598
  *
599
+ * @remarks
600
+ * A `Defect` has no public constructor — it only arises at a boundary (e.g. a
601
+ * callback throwing inside a combinator). This guard is how you detect one.
602
+ *
539
603
  * @returns `true` when `r` is a `Defect`.
604
+ *
605
+ * @example
606
+ * ```ts
607
+ * import { isDefect, Ok } from "unthrown";
608
+ *
609
+ * // A throw inside a combinator is captured as a Defect:
610
+ * const r = Ok(1).map(() => {
611
+ * throw new Error("boom");
612
+ * });
613
+ * isDefect(r); // => true
614
+ * isDefect(Ok(1)); // => false
615
+ *
616
+ * if (isDefect(r)) r.cause; // unknown, narrowed
617
+ * ```
618
+ *
619
+ * @category Guards
540
620
  */
541
621
  function isDefect(r) {
542
622
  return r.tag === "Defect";
@@ -564,6 +644,24 @@ function isDefect(r) {
564
644
  * .map(({ user, org, label }) => render(user, org, label));
565
645
  * // Result<View, NotFound>
566
646
  * ```
647
+ *
648
+ * @example
649
+ * ```ts
650
+ * import { Do, Ok, Err } from "unthrown";
651
+ *
652
+ * // Ok path — the scope accumulates:
653
+ * Do()
654
+ * .bind("a", () => Ok(2))
655
+ * .let("b", ({ a }) => a * 10)
656
+ * .map(({ a, b }) => a + b); // => Ok(22)
657
+ *
658
+ * // Err path — the first Err short-circuits the rest:
659
+ * Do()
660
+ * .bind("a", () => Err("boom"))
661
+ * .let("b", ({ a }) => a); // => Err("boom")
662
+ * ```
663
+ *
664
+ * @category Do-notation
567
665
  */
568
666
  function Do() {
569
667
  return Ok({});
@@ -613,10 +711,16 @@ function isDefectMarker(x) {
613
711
  * @param value - the possibly-absent value.
614
712
  * @param onAbsent - lazily produces the error for the absent case.
615
713
  *
714
+ * @category Interop
715
+ *
616
716
  * @example
617
717
  * ```ts
618
718
  * import { fromNullable } from "unthrown";
619
- * fromNullable(map.get(key), () => "missing").unwrap();
719
+ *
720
+ * const map = new Map([["a", 1]]);
721
+ * fromNullable(map.get("a"), () => "absent").unwrapOr(0); // => 1
722
+ * fromNullable(map.get("z"), () => "absent"); // => Err("absent")
723
+ * fromNullable(0, () => "absent").unwrapOr(-1); // => 0 (falsy but present)
620
724
  * ```
621
725
  */
622
726
  function fromNullable(value, onAbsent) {
@@ -647,11 +751,21 @@ function fromNullable(value, onAbsent) {
647
751
  * unmodeled by returning `defect(cause)` (the helper passed as its second arg).
648
752
  * @returns a function with the same arguments returning `Result<T, E>`.
649
753
  *
754
+ * @category Interop
755
+ *
650
756
  * @example
651
757
  * ```ts
652
758
  * import { fromThrowable } from "unthrown";
653
- * const parse = fromThrowable(JSON.parse, (cause, defect) => defect(cause));
654
- * parse("{}").unwrap();
759
+ *
760
+ * // Model the parse failure as an `Err`, everything unexpected as a `Defect`.
761
+ * const parse = fromThrowable(
762
+ * (text: string) => JSON.parse(text) as unknown,
763
+ * (cause, defect) =>
764
+ * cause instanceof SyntaxError ? ("invalid_json" as const) : defect(cause),
765
+ * );
766
+ *
767
+ * parse('{"ok":true}').unwrapOr(null); // => { ok: true }
768
+ * parse("nope"); // => Err("invalid_json")
655
769
  * ```
656
770
  */
657
771
  function fromThrowable(fn, qualify) {
@@ -686,17 +800,24 @@ function fromThrowable(fn, qualify) {
686
800
  * @param qualify - triages a rejection `cause` into a modeled `E`, or marks it
687
801
  * unmodeled by returning `defect(cause)` (the helper passed as its second arg).
688
802
  *
803
+ * @category Interop
804
+ *
689
805
  * @example
690
806
  * ```ts
691
807
  * import { fromPromise } from "unthrown";
808
+ *
809
+ * // A rejection with a NotFoundError becomes a modeled `Err`; anything else a Defect.
692
810
  * const user = await fromPromise(fetchUser(id), (cause, defect) =>
693
811
  * cause instanceof NotFoundError ? ("not_found" as const) : defect(cause),
694
812
  * );
813
+ *
814
+ * if (user.isOk()) user.value; // => the fetched user
815
+ * // when fetchUser rejects with NotFoundError: user is Err("not_found")
695
816
  * ```
696
817
  */
697
818
  function fromPromise(promise, qualify) {
698
819
  const triage = qualify;
699
- return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : promise).then((value) => okRes(value), (cause) => qualifyToResult(cause, triage)));
820
+ return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : Promise.resolve(promise)).then((value) => okRes(value), (cause) => qualifyToResult(cause, triage)));
700
821
  }
701
822
  /**
702
823
  * Wrap a `Promise` asserted **not** to fail in any modeled way: any rejection
@@ -709,9 +830,20 @@ function fromPromise(promise, qualify) {
709
830
  *
710
831
  * @typeParam T - the resolved value type.
711
832
  * @param promise - the promise, or a thunk returning one.
833
+ *
834
+ * @category Interop
835
+ *
836
+ * @example
837
+ * ```ts
838
+ * import { fromSafePromise } from "unthrown";
839
+ *
840
+ * (await fromSafePromise(Promise.resolve(3))).unwrap(); // => 3
841
+ * // a rejection becomes a Defect (never a modeled Err):
842
+ * await fromSafePromise(Promise.reject(new Error("boom"))); // => Defect(Error("boom"))
843
+ * ```
712
844
  */
713
845
  function fromSafePromise(promise) {
714
- return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : promise).then((value) => okRes(value), (cause) => defectRes(cause)));
846
+ return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : Promise.resolve(promise)).then((value) => okRes(value), (cause) => defectRes(cause)));
715
847
  }
716
848
  function qualifyToResult(cause, qualify) {
717
849
  try {
@@ -749,8 +881,10 @@ function foldRecord(results) {
749
881
  let firstErr;
750
882
  let firstDefect;
751
883
  const values = {};
752
- for (const [key, r] of Object.entries(results)) if (r.tag === "Defect") firstDefect ??= r;
753
- else if (r.tag === "Err") firstErr ??= r;
884
+ for (const [key, r] of Object.entries(results)) if (r.tag === "Defect") {
885
+ firstDefect ??= r;
886
+ break;
887
+ } else if (r.tag === "Err") firstErr ??= r;
754
888
  else Object.defineProperty(values, key, {
755
889
  value: r.value,
756
890
  enumerable: true,
@@ -771,11 +905,14 @@ function foldRecord(results) {
771
905
  * collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
772
906
  * use {@link allFromDict}.
773
907
  *
908
+ * @category Aggregate
909
+ *
774
910
  * @example
775
911
  * ```ts
776
- * import { all, Ok } from "unthrown";
777
- * all([Ok(1), Ok("a"), Ok(true)]).unwrap(); // [1, "a", true] (typed [number, string, boolean])
778
- * all([Ok(1), Ok(2)] as Result<number, never>[]).unwrap(); // number[]
912
+ * import { all, Ok, Err } from "unthrown";
913
+ *
914
+ * all([Ok(1), Ok("a"), Ok(true)]).unwrap(); // => [1, "a", true] (typed [number, string, boolean])
915
+ * all([Ok(1), Err("e"), Ok(3)]); // => Err("e") (short-circuits on the first Err)
779
916
  * ```
780
917
  */
781
918
  function all(results) {
@@ -791,10 +928,14 @@ function all(results) {
791
928
  * Same folding rules as {@link all}: first `Err` short-circuits, any `Defect`
792
929
  * dominates. This is **not** error accumulation.
793
930
  *
931
+ * @category Aggregate
932
+ *
794
933
  * @example
795
934
  * ```ts
796
- * import { allFromDict, Ok } from "unthrown";
797
- * allFromDict({ id: Ok(1), name: Ok("ada") }).unwrap(); // { id: 1, name: "ada" }
935
+ * import { allFromDict, Ok, Err } from "unthrown";
936
+ *
937
+ * allFromDict({ id: Ok(1), name: Ok("ada") }).unwrap(); // => { id: 1, name: "ada" }
938
+ * allFromDict({ id: Ok(1), name: Err("missing") }); // => Err("missing")
798
939
  * ```
799
940
  */
800
941
  function allFromDict(results) {
@@ -810,10 +951,14 @@ function allFromDict(results) {
810
951
  * short-circuits, any `Defect` dominates. As ever, the returned `AsyncResult`'s
811
952
  * internal promise never rejects. For a **record**, use {@link allFromDictAsync}.
812
953
  *
954
+ * @category Aggregate
955
+ *
813
956
  * @example
814
957
  * ```ts
815
958
  * import { allAsync, fromSafePromise } from "unthrown";
816
- * await allAsync([fromSafePromise(a()), fromSafePromise(b())]);
959
+ *
960
+ * const both = allAsync([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
961
+ * (await both).unwrap(); // => [1, 2]
817
962
  * ```
818
963
  */
819
964
  function allAsync(results) {
@@ -827,10 +972,17 @@ function allAsync(results) {
827
972
  * Resolved concurrently (order preserved), folded with the {@link all} rules,
828
973
  * and the internal promise never rejects.
829
974
  *
975
+ * @category Aggregate
976
+ *
830
977
  * @example
831
978
  * ```ts
832
979
  * import { allFromDictAsync, fromSafePromise } from "unthrown";
833
- * await allFromDictAsync({ a: fromSafePromise(a()), b: fromSafePromise(b()) });
980
+ *
981
+ * const both = allFromDictAsync({
982
+ * a: fromSafePromise(Promise.resolve(1)),
983
+ * b: fromSafePromise(Promise.resolve("x")),
984
+ * });
985
+ * (await both).unwrap(); // => { a: 1, b: "x" }
834
986
  * ```
835
987
  */
836
988
  function allFromDictAsync(results) {
@@ -862,10 +1014,12 @@ function allFromDictAsync(results) {
862
1014
  * (`AsyncResult.fromPromise`, `AsyncResult.all`, …), grouped by what they
863
1015
  * return — a static lives in exactly one namespace.
864
1016
  *
1017
+ * @category Facade
1018
+ *
865
1019
  * @example
866
1020
  * ```ts
867
1021
  * import { Result } from "unthrown";
868
- * Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // 2
1022
+ * Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // => 2
869
1023
  * ```
870
1024
  */
871
1025
  const Result = {
@@ -896,10 +1050,13 @@ const Result = {
896
1050
  * {@link Result}, the free functions remain the primary, tree-shakeable API; the
897
1051
  * value `AsyncResult` and the type {@link AsyncResult} share one name.
898
1052
  *
1053
+ * @category Facade
1054
+ *
899
1055
  * @example
900
1056
  * ```ts
901
1057
  * import { AsyncResult } from "unthrown";
902
1058
  * const user = await AsyncResult.fromPromise(fetchUser(id), (c, defect) => defect(c));
1059
+ * user.unwrap(); // => the fetched user (on success)
903
1060
  * ```
904
1061
  */
905
1062
  const AsyncResult = {
@@ -916,9 +1073,17 @@ const AsyncResult = {
916
1073
  *
917
1074
  * @remarks
918
1075
  * Extend the returned class to declare a concrete error. Supply the payload with
919
- * an instantiation expression; omit it for a payload-less error. A `message`
920
- * field in the payload is forwarded to `Error`. The `_tag` always reflects
921
- * `tag` and cannot be overridden by the payload.
1076
+ * an instantiation expression; omit it for a payload-less error. The `message`
1077
+ * is **not** a payload field — it is the human string owned by `Error`, not
1078
+ * structured data, so it is reserved. Define it once per subclass the standard
1079
+ * way, `override message = "…"` (it may interpolate the payload via `this`,
1080
+ * which the base populates before the subclass field initialiser runs); a
1081
+ * payload `message` is rejected at compile time, so contextual detail lives in
1082
+ * typed fields, never baked into per-call prose. The `_tag` always reflects
1083
+ * `tag` and cannot be overridden by the payload. `name` is likewise reserved —
1084
+ * it is the display label (set it with `options.name`); a payload `name` is
1085
+ * rejected at compile time (and excluded from the instance type), so it can't
1086
+ * shadow `Error.name`.
922
1087
  *
923
1088
  * `_tag` is the discriminant used by {@link matchTags}; `Error.name` is the
924
1089
  * human-facing label in stack traces and logs. By default they coincide, but
@@ -929,11 +1094,14 @@ const AsyncResult = {
929
1094
  * ```ts
930
1095
  * class RetryableError extends TaggedError("@my-lib/RetryableError", {
931
1096
  * name: "RetryableError",
932
- * })<{ message: string }> {}
1097
+ * }) {
1098
+ * override message = "operation failed; safe to retry";
1099
+ * }
933
1100
  *
934
- * const e = new RetryableError({ message: "boom" });
935
- * e._tag; // "@my-lib/RetryableError" — namespaced discriminant
936
- * e.name; // "RetryableError" — clean display name
1101
+ * const e = new RetryableError();
1102
+ * e._tag; // "@my-lib/RetryableError" — namespaced discriminant
1103
+ * e.name; // "RetryableError" — clean display name
1104
+ * e.message; // "operation failed; safe to retry" — the standard Error.message
937
1105
  * ```
938
1106
  *
939
1107
  * @typeParam Tag - the string literal discriminant.
@@ -941,13 +1109,15 @@ const AsyncResult = {
941
1109
  * @param options - optional overrides. `options.name` sets `Error.name`
942
1110
  * independently of `tag` (defaults to `tag`).
943
1111
  *
1112
+ * @category Tagged errors
1113
+ *
944
1114
  * @example
945
1115
  * ```ts
946
1116
  * class NotFound extends TaggedError("NotFound") {}
947
1117
  * class HttpError extends TaggedError("HttpError")<{ status: number }> {}
948
1118
  *
949
- * new NotFound()._tag; // "NotFound"
950
- * new HttpError({ status: 500 }).status; // 500
1119
+ * new NotFound()._tag; // => "NotFound"
1120
+ * new HttpError({ status: 500 }).status; // => 500
951
1121
  * ```
952
1122
  */
953
1123
  function TaggedError(tag, options) {
@@ -955,10 +1125,11 @@ function TaggedError(tag, options) {
955
1125
  class TaggedErrorBase extends Error {
956
1126
  _tag;
957
1127
  constructor(props) {
958
- super(typeof props?.["message"] === "string" ? props["message"] : void 0);
1128
+ super();
959
1129
  if (props) Object.assign(this, props);
960
1130
  this._tag = tag;
961
1131
  this.name = displayName;
1132
+ delete this.message;
962
1133
  Object.setPrototypeOf(this, new.target.prototype);
963
1134
  }
964
1135
  }
@@ -966,8 +1137,9 @@ function TaggedError(tag, options) {
966
1137
  }
967
1138
  function matchTags(result, handlers) {
968
1139
  const onErr = (error) => {
969
- const handler = handlers[error._tag];
970
- return handler(error);
1140
+ const tag = error._tag;
1141
+ const handler = tag === "Ok" || tag === "Defect" || !Object.hasOwn(handlers, tag) ? void 0 : handlers[tag];
1142
+ return handler ? handler(error) : handlers.Defect(error);
971
1143
  };
972
1144
  return result.match({
973
1145
  ok: handlers.Ok,