unthrown 5.0.0-beta.1 → 5.0.0-beta.11
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 +14 -5
- package/dist/index.cjs +229 -65
- package/dist/index.d.cts +383 -114
- package/dist/index.d.mts +383 -114
- package/dist/index.mjs +227 -55
- package/package.json +2 -5
- package/dist/index.d.cts.map +0 -1
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
pnpm add unthrown
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
No peer dependencies — the exhaustive error matcher is built-in and exported as
|
|
14
|
+
`match` / `P`.
|
|
15
|
+
|
|
13
16
|
```ts
|
|
14
17
|
import { fromPromise, P, TaggedError } from "unthrown";
|
|
15
18
|
|
|
@@ -22,23 +25,29 @@ const user = fromPromise(fetchUser(id), (cause, defect) =>
|
|
|
22
25
|
|
|
23
26
|
const status = await user.match({
|
|
24
27
|
ok: () => 200,
|
|
25
|
-
|
|
28
|
+
// `errCases` takes the exhaustive matcher — every case of E named:
|
|
29
|
+
errCases: (matcher) => matcher.with(P.tag("NotFound"), () => 404),
|
|
26
30
|
defect: () => 500,
|
|
27
31
|
});
|
|
28
32
|
```
|
|
29
33
|
|
|
30
34
|
- **Errors as values** via `Result<T, E>` / `AsyncResult<T, E>`.
|
|
31
35
|
- **A separate defect channel** for the unexpected — invisible to the type,
|
|
32
|
-
observable only via `match` / `recoverDefect
|
|
36
|
+
observable only via `match` / `recoverDefect` and the `tapDefect` /
|
|
37
|
+
`tapFailure` observers.
|
|
33
38
|
- **Qualification at every boundary** — `fromPromise` / `fromThrowable` force you
|
|
34
39
|
to triage each failure into a modeled error or a defect.
|
|
35
|
-
- **Tagged errors** — `TaggedError(tag)` + `tag(t)`, folded exhaustively through
|
|
36
|
-
`match`'s
|
|
37
|
-
-
|
|
40
|
+
- **Tagged errors** — `TaggedError(tag)` + `P.tag(t)`, folded exhaustively through
|
|
41
|
+
`match`'s built-in error matcher.
|
|
42
|
+
- **Zero runtime dependencies** (the matcher is built-in), ESM-first, dual
|
|
43
|
+
CJS/ESM.
|
|
38
44
|
|
|
39
45
|
See the [full documentation](https://btravstack.github.io/unthrown/) for the guide
|
|
40
46
|
and complete API.
|
|
41
47
|
|
|
48
|
+
**Upgrading from 4.x?** See
|
|
49
|
+
[Upgrade from 4.x to 5.0](https://btravstack.github.io/unthrown/how-to/upgrade-to-v5).
|
|
50
|
+
|
|
42
51
|
## License
|
|
43
52
|
|
|
44
53
|
[MIT](https://github.com/btravstack/unthrown/blob/main/LICENSE) © Benoit TRAVERS
|
package/dist/index.cjs
CHANGED
|
@@ -1,5 +1,179 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
|
|
2
|
+
//#region src/matcher.ts
|
|
3
|
+
/**
|
|
4
|
+
* Cross-copy brand for `P.*` pattern objects: `Symbol.for` yields the same
|
|
5
|
+
* symbol in every copy of the library (dual CJS/ESM, duplicated install,
|
|
6
|
+
* another realm), so a pattern built by one copy is recognised by another —
|
|
7
|
+
* the same rationale as `isResult`'s prototype brand.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
const PATTERN_BRAND = Symbol.for("unthrown.matcher.pattern");
|
|
12
|
+
/**
|
|
13
|
+
* Thrown by `.run()` / `.exhaustive()` when no arm matched the value. For
|
|
14
|
+
* well-typed callers the match is exhaustive by construction, so this is only
|
|
15
|
+
* reachable by a value that slipped past the types (a widened cast, a raw-JS
|
|
16
|
+
* caller); inside the error combinators the throw-to-defect net converts it to
|
|
17
|
+
* a `Defect`, and at the `match` edge it surfaces (a genuinely unmodeled value
|
|
18
|
+
* is a bug).
|
|
19
|
+
*
|
|
20
|
+
* @category Errors
|
|
21
|
+
*/
|
|
22
|
+
var NonExhaustiveError = class extends Error {
|
|
23
|
+
/** The value no arm matched. */
|
|
24
|
+
input;
|
|
25
|
+
constructor(input) {
|
|
26
|
+
let printed;
|
|
27
|
+
try {
|
|
28
|
+
printed = JSON.stringify(input);
|
|
29
|
+
} catch {
|
|
30
|
+
printed = String(input);
|
|
31
|
+
}
|
|
32
|
+
super(`unthrown: no pattern matched the value ${printed}`);
|
|
33
|
+
this.name = "NonExhaustiveError";
|
|
34
|
+
this.input = input;
|
|
35
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Is `x` a *plain* object (prototype `Object.prototype` or `null`) — an object
|
|
40
|
+
* literal, the only object shape that acts as a structural pattern?
|
|
41
|
+
*
|
|
42
|
+
* @internal
|
|
43
|
+
*/
|
|
44
|
+
function isPlainObject(x) {
|
|
45
|
+
const proto = Object.getPrototypeOf(x);
|
|
46
|
+
return proto === Object.prototype || proto === null;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Runtime test: does `pattern` match `value`? A branded `P.*` pattern applies
|
|
50
|
+
* its predicate; a **plain-object** pattern (an object literal, e.g. the
|
|
51
|
+
* `{ _tag }` produced by `P.tag()`) matches when every key matches recursively
|
|
52
|
+
* (extra keys on the value are ignored — matching is structural); anything
|
|
53
|
+
* else — primitives, but also class instances, arrays, and foreign pattern
|
|
54
|
+
* objects (e.g. a real ts-pattern matcher, whose keys are symbols) — is
|
|
55
|
+
* compared with `Object.is`. Restricting structural matching to plain objects
|
|
56
|
+
* is load-bearing: a keyless non-plain object (`new Date()`, `new Error()`, a
|
|
57
|
+
* symbol-keyed foreign pattern) would otherwise vacuously match *every* object
|
|
58
|
+
* via an empty `Object.entries`.
|
|
59
|
+
*
|
|
60
|
+
* @internal
|
|
61
|
+
*/
|
|
62
|
+
function matches(pattern, value) {
|
|
63
|
+
if (typeof pattern === "object" && pattern !== null) {
|
|
64
|
+
const predicate = pattern[PATTERN_BRAND];
|
|
65
|
+
if (typeof predicate === "function") return predicate(value);
|
|
66
|
+
if (!isPlainObject(pattern) || Object.getOwnPropertySymbols(pattern).length > 0) return Object.is(pattern, value);
|
|
67
|
+
if (typeof value !== "object" || value === null) return false;
|
|
68
|
+
return Object.entries(pattern).every(([key, sub]) => matches(sub, value[key]));
|
|
69
|
+
}
|
|
70
|
+
return Object.is(pattern, value);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The runtime builder: first matching arm wins; later arms are skipped once a
|
|
74
|
+
* result is captured. `exhaustive` is a *method* at runtime (the conditional
|
|
75
|
+
* type gates its callability per instantiation).
|
|
76
|
+
*
|
|
77
|
+
* @internal
|
|
78
|
+
*/
|
|
79
|
+
var MatcherImpl = class {
|
|
80
|
+
#value;
|
|
81
|
+
#matched = false;
|
|
82
|
+
#result;
|
|
83
|
+
constructor(value) {
|
|
84
|
+
this.#value = value;
|
|
85
|
+
}
|
|
86
|
+
with(...args) {
|
|
87
|
+
if (this.#matched) return this;
|
|
88
|
+
const handler = args[args.length - 1];
|
|
89
|
+
for (let i = 0; i < args.length - 1; i++) if (matches(args[i], this.#value)) {
|
|
90
|
+
this.#matched = true;
|
|
91
|
+
this.#result = handler(this.#value);
|
|
92
|
+
return this;
|
|
93
|
+
}
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Type-level only — pinning the output type has no runtime meaning, so the
|
|
98
|
+
* builder is returned unchanged (as ts-pattern does).
|
|
99
|
+
*/
|
|
100
|
+
returnType() {
|
|
101
|
+
return this;
|
|
102
|
+
}
|
|
103
|
+
exhaustive() {
|
|
104
|
+
if (this.#matched) return this.#result;
|
|
105
|
+
throw new NonExhaustiveError(this.#value);
|
|
106
|
+
}
|
|
107
|
+
run() {
|
|
108
|
+
return this.exhaustive();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
Object.freeze(MatcherImpl.prototype);
|
|
112
|
+
/**
|
|
113
|
+
* Begin a match over `value`. Chain `.with(pattern, …patterns, handler)` arms;
|
|
114
|
+
* terminate with `.exhaustive()` — or return the un-terminated builder to an
|
|
115
|
+
* unthrown error combinator / `match({ errCases })`, which runs it for you.
|
|
116
|
+
*
|
|
117
|
+
* @remarks
|
|
118
|
+
* This is unthrown's own matcher (the former ts-pattern re-export): the same
|
|
119
|
+
* call-site shape, with exhaustiveness computed by plain `Exclude` over the
|
|
120
|
+
* builder's `Remaining` parameter. Name every case of the input union; the
|
|
121
|
+
* `P._` catch-all is the escape hatch, and is provably exhaustive even over an
|
|
122
|
+
* unresolved generic input — one of the two cases it is irreplaceable for (see
|
|
123
|
+
* {@link P}).
|
|
124
|
+
*
|
|
125
|
+
* @category Constructors
|
|
126
|
+
*/
|
|
127
|
+
function match(value) {
|
|
128
|
+
return new MatcherImpl(value);
|
|
129
|
+
}
|
|
130
|
+
/** @internal */
|
|
131
|
+
function pattern(predicate) {
|
|
132
|
+
return Object.freeze({ [PATTERN_BRAND]: predicate });
|
|
133
|
+
}
|
|
134
|
+
const universal = pattern(() => true);
|
|
135
|
+
/**
|
|
136
|
+
* The pattern namespace (unthrown's own; the former ts-pattern `P`):
|
|
137
|
+
*
|
|
138
|
+
* - `P._` / `P.any` — the universal catch-all, and an **escape hatch** rather
|
|
139
|
+
* than the default: matching the error channel means naming its cases, so
|
|
140
|
+
* reach for this only where they cannot be named. Matches anything, and
|
|
141
|
+
* (because its phantom type is `unknown`) makes the builder provably
|
|
142
|
+
* exhaustive even when the matched input is an unresolved type parameter.
|
|
143
|
+
* Two situations are legitimate: a **helper generic in `E`**, where no arm
|
|
144
|
+
* list can prove exhaustiveness against an unresolved type parameter; and an
|
|
145
|
+
* **`E` that is a single type**, not a union of cases (a validator's issues
|
|
146
|
+
* array, say), where one arm _is_ the enumeration. `@unthrown/oxlint`'s
|
|
147
|
+
* `no-catch-all-pattern` (in its `recommended` preset) flags every other use;
|
|
148
|
+
* keep the deliberate ones behind a targeted `oxlint-disable` saying which of
|
|
149
|
+
* the two it is.
|
|
150
|
+
* - `P.tag<const Tag extends string>(value: Tag): { _tag: Tag }` — the
|
|
151
|
+
* `{ _tag: t }` object pattern, matching any value whose `_tag` equals `t` (a
|
|
152
|
+
* `TaggedError`, or any `_tag`-discriminated member) and narrowing the
|
|
153
|
+
* branch's parameter to that variant, payload included. The workhorse of the
|
|
154
|
+
* error channel: `matcher.with(P.tag("NotFound"), (e) => …)`. It composes like
|
|
155
|
+
* any other pattern — in a grouped arm
|
|
156
|
+
* (`.with(P.tag("A"), P.tag("B"), handler)`) and inside `P.union`.
|
|
157
|
+
* - `P.instanceOf(Cls)` — an `instanceof` check, narrowing to the class
|
|
158
|
+
* instance type (for union members that are not tagged, e.g. a third-party
|
|
159
|
+
* error class).
|
|
160
|
+
* - `P.when(guard)` — an arbitrary type-guard predicate.
|
|
161
|
+
* - `P.union(…patterns)` — matches when any sub-pattern matches.
|
|
162
|
+
* - `P.string` / `P.number` — primitive-type wildcards.
|
|
163
|
+
*
|
|
164
|
+
* @category Constructors
|
|
165
|
+
*/
|
|
166
|
+
const P = Object.freeze({
|
|
167
|
+
_: universal,
|
|
168
|
+
any: universal,
|
|
169
|
+
tag: (value) => ({ _tag: value }),
|
|
170
|
+
instanceOf: (cls) => pattern((value) => value instanceof cls),
|
|
171
|
+
when: (guard) => pattern(guard),
|
|
172
|
+
union: (...patterns) => pattern((value) => patterns.some((sub) => matches(sub, value))),
|
|
173
|
+
string: pattern((value) => typeof value === "string"),
|
|
174
|
+
number: pattern((value) => typeof value === "number")
|
|
175
|
+
});
|
|
176
|
+
//#endregion
|
|
3
177
|
//#region src/defect.ts
|
|
4
178
|
const DEFECT = Symbol("unthrown/Defect");
|
|
5
179
|
/**
|
|
@@ -152,7 +326,7 @@ var Res = class {
|
|
|
152
326
|
return defectRes(cause);
|
|
153
327
|
}
|
|
154
328
|
}
|
|
155
|
-
|
|
329
|
+
mapErrCases(f) {
|
|
156
330
|
if (this.tag !== "Err") return passThrough(this);
|
|
157
331
|
try {
|
|
158
332
|
const out = runMatch(f, this.error);
|
|
@@ -162,7 +336,7 @@ var Res = class {
|
|
|
162
336
|
return defectRes(cause);
|
|
163
337
|
}
|
|
164
338
|
}
|
|
165
|
-
|
|
339
|
+
flatMapErrCases(f) {
|
|
166
340
|
if (this.tag !== "Err") return passThrough(this);
|
|
167
341
|
try {
|
|
168
342
|
const out = runMatch(f, this.error);
|
|
@@ -173,7 +347,7 @@ var Res = class {
|
|
|
173
347
|
return defectRes(cause);
|
|
174
348
|
}
|
|
175
349
|
}
|
|
176
|
-
|
|
350
|
+
recoverErrCases(f) {
|
|
177
351
|
if (this.tag !== "Err") return passThrough(this);
|
|
178
352
|
try {
|
|
179
353
|
const out = runMatch(f, this.error);
|
|
@@ -183,19 +357,21 @@ var Res = class {
|
|
|
183
357
|
return defectRes(cause);
|
|
184
358
|
}
|
|
185
359
|
}
|
|
186
|
-
|
|
360
|
+
tapErrCases(f) {
|
|
187
361
|
if (this.tag !== "Err") return this;
|
|
188
362
|
try {
|
|
189
|
-
runMatch(f, this.error);
|
|
363
|
+
const out = runMatch(f, this.error);
|
|
364
|
+
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, this.error);
|
|
190
365
|
return this;
|
|
191
366
|
} catch (cause) {
|
|
192
367
|
return observerThrowToDefect(cause, this.error);
|
|
193
368
|
}
|
|
194
369
|
}
|
|
195
|
-
|
|
370
|
+
flatTapErrCases(f) {
|
|
196
371
|
if (this.tag !== "Err") return this;
|
|
197
372
|
try {
|
|
198
373
|
const r = runMatch(f, this.error);
|
|
374
|
+
if (isDefectMarker(r)) return observerThrowToDefect(r.cause, this.error);
|
|
199
375
|
if (!isResult(r)) return nonResultCallbackDefect();
|
|
200
376
|
return r.tag === "Ok" ? this : passThrough(r);
|
|
201
377
|
} catch (cause) {
|
|
@@ -232,7 +408,7 @@ var Res = class {
|
|
|
232
408
|
match(cases) {
|
|
233
409
|
switch (this.tag) {
|
|
234
410
|
case "Ok": return cases.ok(this.value);
|
|
235
|
-
case "Err": return cases.
|
|
411
|
+
case "Err": return cases.errCases(match(this.error)).run();
|
|
236
412
|
case "Defect": return cases.defect(this.cause);
|
|
237
413
|
}
|
|
238
414
|
}
|
|
@@ -350,14 +526,18 @@ function defectRes(cause) {
|
|
|
350
526
|
*
|
|
351
527
|
* @example
|
|
352
528
|
* ```ts
|
|
353
|
-
* import { isResult, Ok } from "unthrown";
|
|
529
|
+
* import { isResult, Ok, P } from "unthrown";
|
|
354
530
|
*
|
|
355
531
|
* isResult(Ok(1)); // => true
|
|
356
532
|
* isResult({ tag: "Ok" }); // => false (look-alike, wrong prototype)
|
|
357
533
|
* isResult(Ok(1).toAsync()); // => false (an AsyncResult is not a Result)
|
|
358
534
|
*
|
|
359
535
|
* const x: unknown = Ok(1);
|
|
360
|
-
* if (isResult(x))
|
|
536
|
+
* if (isResult(x))
|
|
537
|
+
* // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
|
|
538
|
+
* // so the `P._` escape hatch is the only arm that can terminate the match:
|
|
539
|
+
* // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
|
|
540
|
+
* x.match({ ok: () => 1, errCases: (m) => m.with(P._, () => 0), defect: () => -1 });
|
|
361
541
|
* ```
|
|
362
542
|
*
|
|
363
543
|
* @category Guards
|
|
@@ -407,19 +587,24 @@ function nonResultCallbackDefect() {
|
|
|
407
587
|
* @internal
|
|
408
588
|
*/
|
|
409
589
|
function runMatch(f, error) {
|
|
410
|
-
return f(
|
|
590
|
+
return f(match(error), defect).run();
|
|
411
591
|
}
|
|
412
592
|
/**
|
|
413
|
-
* A throw inside a *failure observer* (`
|
|
414
|
-
* must not destroy the failure being observed
|
|
415
|
-
* failing error-logger) where losing the
|
|
416
|
-
* resulting Defect aggregates both:
|
|
417
|
-
* `errors[1]` the original failure.
|
|
593
|
+
* A throw inside a *failure observer* (`tapErrCases` / `tapDefect` /
|
|
594
|
+
* `tapFailure` / `flatTapErrCases`) must not destroy the failure being observed
|
|
595
|
+
* — that is the exact place (e.g. a failing error-logger) where losing the
|
|
596
|
+
* underlying failure hurts most. The resulting Defect aggregates both:
|
|
597
|
+
* `errors[0]` is the observer's own failure, `errors[1]` the original failure.
|
|
598
|
+
*
|
|
599
|
+
* An observer branch returning the injected `defect(cause)` marker
|
|
600
|
+
* (`tapErrCases` / `flatTapErrCases`) takes the same route: it is the
|
|
601
|
+
* lint-clean, expression-position form of a `throw` (Thesis #5), so it must not
|
|
602
|
+
* behave differently from one.
|
|
418
603
|
*
|
|
419
604
|
* @internal
|
|
420
605
|
*/
|
|
421
606
|
function observerThrowToDefect(thrown, original) {
|
|
422
|
-
return defectRes(new AggregateError([thrown, original], "unthrown: a failure-observer callback
|
|
607
|
+
return defectRes(new AggregateError([thrown, original], "unthrown: a failure-observer callback failed; errors[0] is the callback's failure (a throw, or a deliberate defect), errors[1] the original failure"));
|
|
423
608
|
}
|
|
424
609
|
/**
|
|
425
610
|
* Validate that a `bind`/`let` scope is a real (non-null) object before merging a
|
|
@@ -547,7 +732,7 @@ var AsyncRes = class AsyncRes {
|
|
|
547
732
|
}
|
|
548
733
|
}));
|
|
549
734
|
}
|
|
550
|
-
|
|
735
|
+
mapErrCases(f) {
|
|
551
736
|
return new AsyncRes(this.#promise.then((r) => {
|
|
552
737
|
if (r.tag !== "Err") return passThrough(r);
|
|
553
738
|
try {
|
|
@@ -559,7 +744,7 @@ var AsyncRes = class AsyncRes {
|
|
|
559
744
|
}
|
|
560
745
|
}));
|
|
561
746
|
}
|
|
562
|
-
|
|
747
|
+
flatMapErrCases(f) {
|
|
563
748
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
564
749
|
if (r.tag !== "Err") return passThrough(r);
|
|
565
750
|
try {
|
|
@@ -573,7 +758,7 @@ var AsyncRes = class AsyncRes {
|
|
|
573
758
|
}
|
|
574
759
|
}));
|
|
575
760
|
}
|
|
576
|
-
|
|
761
|
+
recoverErrCases(f) {
|
|
577
762
|
return new AsyncRes(this.#promise.then((r) => {
|
|
578
763
|
if (r.tag !== "Err") return passThrough(r);
|
|
579
764
|
try {
|
|
@@ -585,22 +770,25 @@ var AsyncRes = class AsyncRes {
|
|
|
585
770
|
}
|
|
586
771
|
}));
|
|
587
772
|
}
|
|
588
|
-
|
|
773
|
+
tapErrCases(f) {
|
|
589
774
|
return new AsyncRes(this.#promise.then((r) => {
|
|
590
775
|
if (r.tag !== "Err") return r;
|
|
591
776
|
try {
|
|
592
|
-
runMatch(f, r.error);
|
|
777
|
+
const out = runMatch(f, r.error);
|
|
778
|
+
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
593
779
|
return r;
|
|
594
780
|
} catch (cause) {
|
|
595
781
|
return observerThrowToDefect(cause, r.error);
|
|
596
782
|
}
|
|
597
783
|
}));
|
|
598
784
|
}
|
|
599
|
-
|
|
785
|
+
flatTapErrCases(f) {
|
|
600
786
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
601
787
|
if (r.tag !== "Err") return passThrough(r);
|
|
602
788
|
try {
|
|
603
|
-
const
|
|
789
|
+
const out = runMatch(f, r.error);
|
|
790
|
+
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
791
|
+
const inner = await out;
|
|
604
792
|
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
605
793
|
return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
|
|
606
794
|
} catch (cause) {
|
|
@@ -1006,6 +1194,11 @@ function fromSafeThrowable(fn) {
|
|
|
1006
1194
|
* @param promise - the promise, or a thunk returning one.
|
|
1007
1195
|
* @param qualify - triages a rejection `cause` into a modeled `E`, or marks it
|
|
1008
1196
|
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
1197
|
+
* @param _guard - compile-time only; never pass it. The phantom rest-tuple that
|
|
1198
|
+
* enforces "qualify is synchronous": an `async` qualify makes this demand an
|
|
1199
|
+
* impossible extra argument (whose type spells out the error), while a
|
|
1200
|
+
* synchronous one leaves it empty. Encoded here — not on `qualify`'s return
|
|
1201
|
+
* type — so `T`'s inference from `promise` is undisturbed.
|
|
1009
1202
|
*
|
|
1010
1203
|
* @category Interop
|
|
1011
1204
|
*
|
|
@@ -1022,7 +1215,7 @@ function fromSafeThrowable(fn) {
|
|
|
1022
1215
|
* // when fetchUser rejects with NotFoundError: user is Err("not_found")
|
|
1023
1216
|
* ```
|
|
1024
1217
|
*/
|
|
1025
|
-
function fromPromise(promise, qualify) {
|
|
1218
|
+
function fromPromise(promise, qualify, ..._guard) {
|
|
1026
1219
|
const triage = qualify;
|
|
1027
1220
|
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : Promise.resolve(promise)).then((value) => okRes(value), (cause) => qualifyToResult(cause, triage)));
|
|
1028
1221
|
}
|
|
@@ -1336,10 +1529,14 @@ const AsyncResult = {
|
|
|
1336
1529
|
* so a payload `cause` (e.g. a wrapped driver error) is a legitimate,
|
|
1337
1530
|
* *narrowing* structured field.
|
|
1338
1531
|
*
|
|
1339
|
-
*
|
|
1340
|
-
*
|
|
1341
|
-
*
|
|
1342
|
-
*
|
|
1532
|
+
* The matching half of the convention is `P.tag(t)` — the pattern constructor on
|
|
1533
|
+
* the `P` namespace, which builds the `{ _tag: t }` pattern this factory's `_tag`
|
|
1534
|
+
* is selected by (there is no standalone `tag` export).
|
|
1535
|
+
*
|
|
1536
|
+
* `_tag` is the discriminant matched by `P.tag` in the error combinators
|
|
1537
|
+
* (`result.mapErrCases((matcher) => matcher.with(P.tag("NotFound"), …))`) and in
|
|
1538
|
+
* `match`'s `errCases` handler; `Error.name` is the human-facing label in stack
|
|
1539
|
+
* traces and logs. By default they coincide, but
|
|
1343
1540
|
* they can be **decoupled** with `options.name` — so a tag can be namespaced for
|
|
1344
1541
|
* collision-safety (`"@my-lib/RetryableError"`) without that slash-prefixed
|
|
1345
1542
|
* string leaking into `Error.name`:
|
|
@@ -1398,29 +1595,6 @@ function TaggedError(tag, options) {
|
|
|
1398
1595
|
}
|
|
1399
1596
|
return TaggedErrorBase;
|
|
1400
1597
|
}
|
|
1401
|
-
/**
|
|
1402
|
-
* A `ts-pattern` pattern matching any value whose `_tag` equals `value` — a
|
|
1403
|
-
* {@link TaggedError}, or any discriminated member. Equivalent to the object
|
|
1404
|
-
* pattern `{ _tag: value }`, but reads better inside an error-matching
|
|
1405
|
-
* combinator and narrows to the matching variant, payload included.
|
|
1406
|
-
*
|
|
1407
|
-
* @typeParam Tag - the string literal tag to match.
|
|
1408
|
-
* @param value - the `_tag` to match.
|
|
1409
|
-
*
|
|
1410
|
-
* @category Tagged errors
|
|
1411
|
-
*
|
|
1412
|
-
* @example
|
|
1413
|
-
* ```ts
|
|
1414
|
-
* result.mapErr((matcher) =>
|
|
1415
|
-
* matcher
|
|
1416
|
-
* .with(tag("NotFound"), () => new NotFoundException())
|
|
1417
|
-
* .with(tag("Conflict"), (e) => new ConflictException(e.key)),
|
|
1418
|
-
* );
|
|
1419
|
-
* ```
|
|
1420
|
-
*/
|
|
1421
|
-
function tag(value) {
|
|
1422
|
-
return { _tag: value };
|
|
1423
|
-
}
|
|
1424
1598
|
//#endregion
|
|
1425
1599
|
exports.AsyncResult = AsyncResult;
|
|
1426
1600
|
exports.Do = Do;
|
|
@@ -1428,14 +1602,10 @@ exports.DoAsync = DoAsync;
|
|
|
1428
1602
|
exports.Err = Err;
|
|
1429
1603
|
exports.ErrAsync = ErrAsync;
|
|
1430
1604
|
exports.GetError = GetError;
|
|
1605
|
+
exports.NonExhaustiveError = NonExhaustiveError;
|
|
1431
1606
|
exports.Ok = Ok;
|
|
1432
1607
|
exports.OkAsync = OkAsync;
|
|
1433
|
-
|
|
1434
|
-
enumerable: true,
|
|
1435
|
-
get: function() {
|
|
1436
|
-
return ts_pattern.P;
|
|
1437
|
-
}
|
|
1438
|
-
});
|
|
1608
|
+
exports.P = P;
|
|
1439
1609
|
exports.Result = Result;
|
|
1440
1610
|
exports.TaggedError = TaggedError;
|
|
1441
1611
|
exports.all = all;
|
|
@@ -1451,10 +1621,4 @@ exports.isDefect = isDefect;
|
|
|
1451
1621
|
exports.isErr = isErr;
|
|
1452
1622
|
exports.isOk = isOk;
|
|
1453
1623
|
exports.isResult = isResult;
|
|
1454
|
-
|
|
1455
|
-
enumerable: true,
|
|
1456
|
-
get: function() {
|
|
1457
|
-
return ts_pattern.match;
|
|
1458
|
-
}
|
|
1459
|
-
});
|
|
1460
|
-
exports.tag = tag;
|
|
1624
|
+
exports.match = match;
|