unthrown 4.2.0 → 5.0.0-beta.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/dist/index.d.cts CHANGED
@@ -1,3 +1,25 @@
1
+ import { P, match, match as match$1 } from "ts-pattern";
2
+ //#region src/defect.d.ts
3
+ declare const DEFECT: unique symbol;
4
+ /**
5
+ * The opaque marker a `qualify` function returns to triage a cause as
6
+ * **unexpected**.
7
+ *
8
+ * @remarks
9
+ * `qualify` (passed to {@link fromPromise} / {@link fromThrowable}) returns
10
+ * `E | Defect`: either a modeled domain error, or a `Defect` produced by the
11
+ * injected `defect` helper to say "this failure is not modeled". A `Defect` is
12
+ * opaque — it carries the original cause for the boundary to convert into the
13
+ * third runtime state of a `Result`. It is **not** a public value; the only way
14
+ * to mint one is the `defect` helper the boundary passes to `qualify`.
15
+ *
16
+ * @internal
17
+ */
18
+ type Defect = {
19
+ readonly [DEFECT]: true;
20
+ readonly cause: unknown;
21
+ };
22
+ //#endregion
1
23
  //#region src/types.d.ts
2
24
  /**
3
25
  * Flatten an intersection into a single object literal so accumulated `bind` /
@@ -32,6 +54,49 @@ type Bound<T, K extends string, U> = Prettify<Omit<T, K> & { readonly [P in K]:
32
54
  * @category Types
33
55
  */
34
56
  type NotThenable<R> = [R] extends [PromiseLike<unknown>] ? "unthrown: combinator callbacks are synchronous — lift async work with fromPromise and compose with flatMap" : unknown;
57
+ /**
58
+ * The ts-pattern match builder over an error union `E`, as produced by
59
+ * `match(error)`. This is what an error combinator's callback receives — chain
60
+ * `.with(pattern, handler)` on it; the combinator itself calls `.exhaustive()`,
61
+ * so the callback returns the **un-terminated** builder.
62
+ *
63
+ * @remarks
64
+ * Named via `ReturnType<typeof match<E>>` so the internal ts-pattern `Match`
65
+ * type (not part of ts-pattern's public exports) never has to be imported.
66
+ *
67
+ * @typeParam E - the error union being matched.
68
+ * @category Types
69
+ */
70
+ type ErrMatcher<E> = ReturnType<typeof match$1<E>>;
71
+ /**
72
+ * The shape an error-combinator callback must return: an **exhaustive**
73
+ * ts-pattern builder. `exhaustive` is required to be *callable* — on a builder
74
+ * that hasn't covered every case ts-pattern types it as a `NonExhaustiveError`
75
+ * (not a function), so a non-exhaustive chain fails to satisfy this and errors
76
+ * at the call site. `run` carries the output type.
77
+ *
78
+ * @typeParam O - the union of the branch return types (the builder's output).
79
+ * @internal
80
+ */
81
+ type ExhaustiveMatch<O> = {
82
+ exhaustive: (...args: never[]) => unknown;
83
+ run: () => O;
84
+ };
85
+ /**
86
+ * The output of an `ExhaustiveMatch` — the union of its branch returns.
87
+ *
88
+ * @internal
89
+ */
90
+ type MatchOut<M> = M extends ExhaustiveMatch<infer O> ? O : never;
91
+ /**
92
+ * The outgoing modeled-error type a transforming match produces: the builder's
93
+ * output with the `Defect` arm **subtracted** (`Exclude<O, Defect>`, the same
94
+ * inference as the boundary `qualify`, Thesis #3). A branch that returns
95
+ * `defect(cause)` therefore contributes nothing to the modeled channel.
96
+ *
97
+ * @internal
98
+ */
99
+ type MatchErrOut<M> = Exclude<MatchOut<M>, Defect>;
35
100
  /**
36
101
  * The fluent method surface every {@link Result} variant carries — the
37
102
  * combinators (`map`, `flatMap`, `mapErr`, `match`, `get`, …), documented one
@@ -48,7 +113,7 @@ type NotThenable<R> = [R] extends [PromiseLike<unknown>] ? "unthrown: combinator
48
113
  * @typeParam E - the modeled error type.
49
114
  * @category Methods
50
115
  */
51
- type ResultMethods<T, E> = {
116
+ type ResultMethods<out T, out E> = {
52
117
  /**
53
118
  * Transform the success value with `f`.
54
119
  *
@@ -162,103 +227,90 @@ type ResultMethods<T, E> = {
162
227
  */
163
228
  discard(): Result$1<void, E>;
164
229
  /**
165
- * Transform the modeled error with `f`.
166
- *
167
- * Runs `f` only on `Err`; `Ok` passes through and a `Defect` is **never**
168
- * touched. If `f` throws, the throw becomes a `Defect`. An async callback is
169
- * rejected at compile time ({@link NotThenable}).
230
+ * Transform the modeled error by **matching it exhaustively** with ts-pattern.
170
231
  *
171
- * @typeParam E2 - the mapped error type.
172
- * @param f - maps the current error to a new one.
173
- */
174
- mapErr<E2>(f: (error: E) => E2 & NotThenable<E2>): Result$1<T, E2>;
175
- /**
176
- * Sequence from an `Err` by producing another `Result` — the error-channel
177
- * mirror of {@link ResultMethods.flatMap | flatMap}.
232
+ * @remarks
233
+ * The callback receives `match(error)` (an {@link ErrMatcher}) and the
234
+ * injected `defect` helper. Chain `.with(pattern, handler)` and **return the
235
+ * un-terminated builder** — `mapErr` calls `.exhaustive()` itself, so a
236
+ * missing case is a compile error at the call site (there is no `.exhaustive()`
237
+ * to forget, and no way to slip in `.otherwise()`). The outgoing error type is
238
+ * the union of the branch returns with the `Defect` arm subtracted
239
+ * (`Exclude<O, Defect>`) — a branch returning `defect(cause)` converts that case
240
+ * to a `Defect` and drops it from `E`. Runs only on `Err`; `Ok` and `Defect`
241
+ * pass through. A branch that throws also becomes a `Defect`.
178
242
  *
179
- * Runs `f` only on `Err`; `Ok` and `Defect` pass through. If `f` throws, the
180
- * throw becomes a `Defect`.
243
+ * `.with(P._, …)` is the deliberate uniform/catch-all (it makes the match
244
+ * exhaustive). Match on anything ts-pattern supports — `_tag`, `code`,
245
+ * structural shape, guards, or grouped patterns `.with(a, b, handler)`.
181
246
  *
182
- * @typeParam U - an alternative success type `f` may produce.
183
- * @typeParam E2 - the error type `f` may produce instead.
184
- * @param f - produces a fallback `Result` from the current error.
247
+ * @typeParam M - the exhaustive builder the callback returns.
248
+ * @param f - builds the match over the error (returns the un-terminated builder).
185
249
  */
186
- flatMapErr<U, E2>(f: (error: E) => Result$1<U, E2>): Result$1<T | U, E2>;
250
+ mapErr<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): Result$1<T, MatchErrOut<M>>;
187
251
  /**
188
- * Sequence from an `Err` by producing another `Result`.
252
+ * Sequence from an `Err` by producing another `Result` — the error-channel
253
+ * mirror of {@link ResultMethods.flatMap | flatMap}, **matching the error
254
+ * exhaustively** ({@link ErrMatcher}; the combinator calls `.exhaustive()`).
189
255
  *
190
- * @deprecated Renamed to {@link ResultMethods.flatMapErr | flatMapErr} — it is
191
- * `flatMap` on the error channel, so it now follows the `…Err` convention. This
192
- * alias will be removed in a future major.
256
+ * Each branch returns a `Result`; the outgoing channels are the unions of the
257
+ * branch-returned `Result`s' channels. A branch may return `defect(cause)`.
258
+ * Runs only on `Err`; `Ok` and `Defect` pass through.
193
259
  *
194
- * @typeParam U - an alternative success type `f` may produce.
195
- * @typeParam E2 - the error type `f` may produce instead.
196
- * @param f - produces a fallback `Result` from the current error.
260
+ * @typeParam M - the exhaustive builder the callback returns.
261
+ * @param f - builds the match; each branch produces a fallback `Result`.
197
262
  */
198
- orElse<U, E2>(f: (error: E) => Result$1<U, E2>): Result$1<T | U, E2>;
263
+ flatMapErr<M extends ExhaustiveMatch<Result$1<unknown, unknown> | Defect>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): Result$1<T | OkOf<MatchOut<M>>, ErrOf<MatchOut<M>>>;
199
264
  /**
200
265
  * Recover from an `Err` by producing a success value, emptying the error
201
- * channel. Pairs with {@link ResultMethods.recoverDefect | recoverDefect}.
266
+ * channel — **matching the error exhaustively** ({@link ErrMatcher}). Pairs
267
+ * with {@link ResultMethods.recoverDefect | recoverDefect}.
202
268
  *
203
269
  * @remarks
204
270
  * The result type is `Result<T | U, never>`, but `never` describes only the
205
- * **error** channel — a `Defect` can still be present at runtime, so do not
206
- * read `never` as "total". Runs `f` only on `Err`; `Ok` and `Defect` pass
207
- * through. If `f` throws, the throw becomes a `Defect`. An async callback is
208
- * rejected at compile time ({@link NotThenable}).
209
- *
210
- * @typeParam U - the recovered success type.
211
- * @param f - produces a success value from the current error.
212
- */
213
- recoverErr<U>(f: (error: E) => U & NotThenable<U>): Result$1<T | U, never>;
214
- /**
215
- * Recover from an `Err` by producing a success value.
216
- *
217
- * @deprecated Renamed to {@link ResultMethods.recoverErr | recoverErr} — it now
218
- * pairs with {@link ResultMethods.recoverDefect | recoverDefect} and follows the
219
- * `…Err` convention. This alias will be removed in a future major.
271
+ * **error** channel — a `Defect` can still be present at runtime. A branch may
272
+ * return `defect(cause)` (which stays a `Defect`, not a recovery). Runs only on
273
+ * `Err`; `Ok` and `Defect` pass through.
220
274
  *
221
- * @typeParam U - the recovered success type.
222
- * @param f - produces a success value from the current error.
275
+ * @typeParam M - the exhaustive builder the callback returns.
276
+ * @param f - builds the match; each branch produces a success value.
223
277
  */
224
- recover<U>(f: (error: E) => U & NotThenable<U>): Result$1<T | U, never>;
278
+ recoverErr<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): Result$1<T | MatchErrOut<M>, never>;
225
279
  /**
226
- * Run a side effect on the error and pass the `Result` through unchanged.
227
- *
228
- * Runs only on `Err`. If `f` throws, the result is a `Defect` whose cause is
229
- * an `AggregateError` of `[thrown, original failure]` — observing a failure
230
- * never destroys it. An async callback is rejected at compile time
231
- * ({@link NotThenable}).
280
+ * Run a side effect on the error — **matched exhaustively** ({@link ErrMatcher})
281
+ * — and pass the `Result` through unchanged.
232
282
  *
233
283
  * @remarks
234
- * As with {@link ResultMethods.tap | tap}, `f`'s return value is ignored — a
235
- * failable `Result`-returning effect belongs in
236
- * {@link ResultMethods.flatTapErr | flatTapErr}; an `AsyncResult`-returning
237
- * one needs the chain lifted with {@link ResultMethods.toAsync | toAsync}
238
- * first (the async {@link AsyncResultMethods.flatTapErr | flatTapErr}
239
- * accepts both).
284
+ * The callback builds a match whose branches run side effects; their return
285
+ * values are ignored and the original `Err` flows through. Exhaustive like the
286
+ * transformers (use `.with(P._, …)` for a catch-all). If a branch throws, the
287
+ * result is a `Defect` whose cause is an `AggregateError` of `[thrown, original
288
+ * failure]` — observing a failure never destroys it. A failable
289
+ * `Result`-returning effect belongs in
290
+ * {@link ResultMethods.flatTapErr | flatTapErr}.
240
291
  *
241
- * @param f - the side effect (its return value is ignored).
292
+ * @param f - builds the match; branch returns are ignored.
242
293
  */
243
- tapErr<R>(f: (error: E) => R & NotThenable<R>): Result$1<T, E>;
294
+ tapErr(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<unknown>): Result$1<T, E>;
244
295
  /**
245
296
  * Run a **failable** side effect on the error, keeping the original error but
246
- * threading the effect's own error.
297
+ * threading the effect's own error — **matched exhaustively**
298
+ * ({@link ErrMatcher}).
247
299
  *
248
300
  * @remarks
249
- * The error-channel mirror of {@link ResultMethods.flatTap | flatTap}: `f`
250
- * returns a `Result`, but its **success value is discarded** — on the effect's
251
- * `Ok` the original `Err` flows through unchanged, while an `Err` (or `Defect`)
252
- * from `f` short-circuits and threads its error (`Result<T, E | E2>`). Runs only
253
- * on `Err`; `Ok` and `Defect` pass through. If `f` throws, the result is a
254
- * `Defect` whose cause is an `AggregateError` of `[thrown, original failure]` —
255
- * observing a failure never destroys it. Use it for a failable effect _during_
256
- * error handling (e.g. writing the error to an audit log that may itself fail).
301
+ * The error-channel mirror of {@link ResultMethods.flatTap | flatTap}: each
302
+ * branch returns a `Result` whose **success value is discarded** — on the
303
+ * effect's `Ok` the original `Err` flows through, while an `Err`/`Defect` from a
304
+ * branch short-circuits and threads its error. Note the asymmetry with a
305
+ * *throw*: a branch that **returns** a `Defect` **replaces** the original `Err`
306
+ * (Defect-dominance, the short-circuit rule — it is not aggregated), whereas a
307
+ * branch that **throws** produces a `Defect` aggregating `[thrown, original
308
+ * failure]` (observing a failure by throwing never destroys it).
257
309
  *
258
- * @typeParam E2 - the error type the effect may introduce.
259
- * @param f - the failable side effect; its `Ok` value is ignored.
310
+ * @typeParam M - the exhaustive builder the callback returns.
311
+ * @param f - builds the match; each branch is a failable effect (its `Ok` is ignored).
260
312
  */
261
- flatTapErr<E2>(f: (error: E) => Result$1<unknown, E2>): Result$1<T, E | E2>;
313
+ flatTapErr<E2>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<Result$1<unknown, E2>>): Result$1<T, E | E2>;
262
314
  /**
263
315
  * Recover from a `Defect` — the **only** combinator that can touch one.
264
316
  *
@@ -284,23 +336,59 @@ type ResultMethods<T, E> = {
284
336
  */
285
337
  tapDefect<R>(f: (cause: unknown) => R & NotThenable<R>): Result$1<T, E>;
286
338
  /**
287
- * Exhaustively fold all three runtime states into a single value of type `R`.
339
+ * Run a side effect on **any failure** — `Err` or `Defect` — and pass the
340
+ * `Result` through unchanged. The one cross-channel observer, for the shared
341
+ * "it went KO" concern (logging, metrics, rollback) that would otherwise be
342
+ * duplicated across {@link ResultMethods.tapErr | tapErr} and
343
+ * {@link ResultMethods.tapDefect | tapDefect}.
344
+ *
345
+ * @remarks
346
+ * `f` receives the narrowed **failure variant** ({@link FailureView}), not a
347
+ * payload — the payload union `E | unknown` would collapse to `unknown` and
348
+ * lose `E`'s typing. Branch on `failure.tag` to reach the typed payload
349
+ * (`"Err"` → `failure.error: E`, `"Defect"` → `failure.cause: unknown`), or
350
+ * treat it opaquely for a shared logger. Runs on `Err` and `Defect`; `Ok`
351
+ * passes through. It **observes without consuming**: the failure flows on
352
+ * unchanged — to also recover, use
353
+ * {@link ResultMethods.recoverErr | recoverErr} /
354
+ * {@link ResultMethods.recoverDefect | recoverDefect} (deliberately separate
355
+ * acts) or {@link ResultMethods.match | match} at the edge. If `f` throws, the
356
+ * result is a `Defect` whose cause is an `AggregateError` of `[thrown,
357
+ * original failure]` — observing a failure never destroys it. An async
358
+ * callback is rejected at compile time ({@link NotThenable}).
359
+ *
360
+ * @param f - the side effect over the failure variant (its return value is ignored).
361
+ */
362
+ tapFailure<R>(f: (failure: FailureView<E, T>) => R & NotThenable<R>): Result$1<T, E>;
363
+ /**
364
+ * Exhaustively fold all three runtime states into a single value.
288
365
  *
289
366
  * @remarks
290
367
  * Exactly one handler runs. Together with the throw-to-Defect guarantee, this
291
368
  * is typically the single place a pipeline is handled at the edge — mapping
292
369
  * `Ok`/`Err`/`Defect` to (for example) 2xx / 4xx / 5xx with no `try`/`catch`.
293
- * (For richer matching, a `Result` is also a discriminated union — branch on
294
- * its `tag` property, e.g. with `ts-pattern`.)
295
370
  *
296
- * @typeParam R - the folded result type.
297
- * @param cases - one handler per channel.
371
+ * The `err` handler does not take a single blanket callback: it receives
372
+ * `match(error)` (an {@link ErrMatcher}) and **matches the error exhaustively**,
373
+ * exactly like the error combinators. Chain `.with(pattern, handler)` and
374
+ * **return the un-terminated builder** — `match` calls `.exhaustive()` itself,
375
+ * so a missing case is a compile error at the call site (no `.exhaustive()` to
376
+ * forget). Use `.with(P._, …)` for a uniform catch-all. Unlike the combinators
377
+ * the branches receive **no `defect` helper** — `match` is total elimination
378
+ * to a value, with no `Defect` output channel; the `defect` case handles a
379
+ * `Result` that already carries one. (A `Result` is also a discriminated
380
+ * union — for richer whole-`Result` matching, `match(result).with(…)`.)
381
+ *
382
+ * @typeParam ROk - the `ok` handler return type.
383
+ * @typeParam RDefect - the `defect` handler return type.
384
+ * @typeParam M - the exhaustive builder the `err` handler returns.
385
+ * @param cases - the `ok`/`defect` handlers plus the `err` matcher builder.
298
386
  */
299
- match<R>(cases: {
300
- ok: (value: T) => R;
301
- err: (error: E) => R;
302
- defect: (cause: unknown) => R;
303
- }): R;
387
+ match<ROk, RDefect, M extends ExhaustiveMatch<unknown>>(cases: {
388
+ ok: (value: T) => ROk;
389
+ err: (matcher: ErrMatcher<E>) => M;
390
+ defect: (cause: unknown) => RDefect;
391
+ }): ROk | RDefect | MatchOut<M>;
304
392
  /**
305
393
  * Extract the success value.
306
394
  *
@@ -318,15 +406,6 @@ type ResultMethods<T, E> = {
318
406
  * @returns the `Ok` value.
319
407
  */
320
408
  get(this: Result$1<T, never>): T;
321
- /**
322
- * Extract the success value.
323
- *
324
- * @deprecated Renamed to {@link ResultMethods.get | get}, unifying the extractor
325
- * family under `get…`. This alias will be removed in a future major.
326
- *
327
- * @returns the `Ok` value.
328
- */
329
- unwrap(this: Result$1<T, never>): T;
330
409
  /**
331
410
  * Extract the modeled error.
332
411
  *
@@ -341,15 +420,6 @@ type ResultMethods<T, E> = {
341
420
  * @returns the `Err` value.
342
421
  */
343
422
  getErr(this: Result$1<never, E>): E;
344
- /**
345
- * Extract the modeled error.
346
- *
347
- * @deprecated Renamed to {@link ResultMethods.getErr | getErr}, unifying the
348
- * extractor family under `get…`. This alias will be removed in a future major.
349
- *
350
- * @returns the `Err` value.
351
- */
352
- unwrapErr(this: Result$1<never, E>): E;
353
423
  /**
354
424
  * The success value, or `fallback` on `Err`.
355
425
  *
@@ -359,17 +429,6 @@ type ResultMethods<T, E> = {
359
429
  * it is never silently replaced.
360
430
  */
361
431
  getOr<U>(fallback: U): T | U;
362
- /**
363
- * The success value, or `fallback` on `Err`.
364
- *
365
- * @deprecated Renamed to {@link ResultMethods.getOr | getOr}, unifying the
366
- * extractor family under `get…`. This alias will be removed in a future major.
367
- *
368
- * @typeParam U - the fallback type (may differ from `T`; the return widens to `T | U`).
369
- * @param fallback - returned when the result is an `Err`.
370
- * @throws Re-throws on a `Defect`.
371
- */
372
- unwrapOr<U>(fallback: U): T | U;
373
432
  /**
374
433
  * The success value, or `f(error)` on `Err`.
375
434
  *
@@ -378,17 +437,6 @@ type ResultMethods<T, E> = {
378
437
  * @throws Re-throws on a `Defect`.
379
438
  */
380
439
  getOrElse<U>(f: (error: E) => U): T | U;
381
- /**
382
- * The success value, or `f(error)` on `Err`.
383
- *
384
- * @deprecated Renamed to {@link ResultMethods.getOrElse | getOrElse}, unifying
385
- * the extractor family under `get…`. This alias will be removed in a future major.
386
- *
387
- * @typeParam U - the fallback type (may differ from `T`; the return widens to `T | U`).
388
- * @param f - lazily computes the fallback from the error.
389
- * @throws Re-throws on a `Defect`.
390
- */
391
- unwrapOrElse<U>(f: (error: E) => U): T | U;
392
440
  /**
393
441
  * The success value, or `null` on `Err`.
394
442
  *
@@ -405,20 +453,26 @@ type ResultMethods<T, E> = {
405
453
  * The success value, or **throw** the modeled error on `Err`.
406
454
  *
407
455
  * @remarks
408
- * A deliberate escape hatch off the errors-as-values model. Unlike
409
- * {@link ResultMethods.get | get} (type-gated to an empty error
410
- * channel), this compiles on any `Result<T, E>` and **throws the `Err` value
411
- * as-is** at the call site. Its purpose is to move a literal `throw` behind a
412
- * method, so a `no-throw` lint rule can ban raw throws while this one
413
- * sanctioned extraction remains — _not_ to replace principled handling. When
414
- * you can keep the error a value, prefer {@link ResultMethods.match | match} /
415
- * {@link ResultMethods.recoverErr | recoverErr} / {@link ResultMethods.flatMapErr | flatMapErr}.
456
+ * A deliberate escape hatch off the errors-as-values model — it **throws the
457
+ * `Err` value as-is** at the call site. Its purpose is to move a literal
458
+ * `throw` behind a method, so a `no-throw` lint rule can ban raw throws while
459
+ * this one sanctioned extraction remains — _not_ to replace principled
460
+ * handling. When you can keep the error a value, prefer
461
+ * {@link ResultMethods.match | match} / {@link ResultMethods.recoverErr | recoverErr} /
462
+ * {@link ResultMethods.flatMapErr | flatMapErr}.
463
+ *
464
+ * Type-gated as the **complement** of {@link ResultMethods.get | get}: it
465
+ * compiles only when the error channel is **non-empty** (`E` is not `never`) —
466
+ * there must be a modeled error for it to throw. On a `Result<T, never>` there
467
+ * is nothing to throw, so `getOrThrow` does not compile; use `get()` (which
468
+ * gates the other way). Together they partition extraction by the error
469
+ * channel's state, with no overlap.
416
470
  *
417
471
  * @returns the `Ok` value.
418
472
  * @throws the modeled `error` on `Err`; re-throws the original `cause` on a
419
473
  * `Defect` (a panic, like the rest of the `getOr…` family).
420
474
  */
421
- getOrThrow(): T;
475
+ getOrThrow(this: [E] extends [never] ? never : Result$1<T, E>): T;
422
476
  /** Whether this result is `Ok` — narrows `this` to its {@link OkView} on `true`. */
423
477
  isOk(): this is OkView<T, E>;
424
478
  /** Whether this result is `Err` — narrows `this` to its {@link ErrView} on `true`. */
@@ -484,6 +538,30 @@ type DefectView<T = never, E = never> = ResultMethods<T, E> & {
484
538
  readonly tag: "Defect";
485
539
  readonly cause: unknown;
486
540
  };
541
+ /**
542
+ * A failure variant of a {@link Result}: an {@link ErrView} **or** a
543
+ * {@link DefectView}. This is what a `tapFailure` callback receives — the
544
+ * discriminated variant rather than a payload, because the payload union
545
+ * `E | unknown` would collapse to `unknown` and lose `E`'s typing. Branch on
546
+ * `tag` to narrow (`"Err"` → `.error: E`, `"Defect"` → `.cause: unknown`).
547
+ *
548
+ * @remarks
549
+ * Like {@link ErrView}, the error type comes **first** (`FailureView<E, T>`) —
550
+ * the error is the payload you are usually here for, and a shared observer can
551
+ * spell just `FailureView<MyError>`.
552
+ *
553
+ * @example
554
+ * ```ts
555
+ * const logKo = (f: FailureView<ApiError>) =>
556
+ * f.tag === "Err" ? logger.warn(f.error) : logger.error(f.cause);
557
+ * result.tapFailure(logKo);
558
+ * ```
559
+ *
560
+ * @typeParam E - the modeled error type.
561
+ * @typeParam T - the success value type (phantom here; a failure carries none).
562
+ * @category Types
563
+ */
564
+ type FailureView<E, T = never> = ErrView<E, T> | DefectView<T, E>;
487
565
  /**
488
566
  * The core type of the library: a computation that has either succeeded with a
489
567
  * value of type `T` or failed with a *modeled* error of type `E`.
@@ -540,7 +618,7 @@ type Result$1<T, E> = OkView<T, E> | ErrView<E, T> | DefectView<T, E>;
540
618
  *
541
619
  * @category Types
542
620
  */
543
- type Awaitable<T> = {
621
+ type Awaitable<out T> = {
544
622
  then<R = T>(onfulfilled?: ((value: T) => R | PromiseLike<R>) | null): PromiseLike<R>;
545
623
  };
546
624
  /**
@@ -563,7 +641,7 @@ type Awaitable<T> = {
563
641
  * @typeParam E - the modeled error type.
564
642
  * @category Methods
565
643
  */
566
- type AsyncResultMethods<T, E> = {
644
+ type AsyncResultMethods<out T, out E> = {
567
645
  /**
568
646
  * Asynchronous {@link ResultMethods.map | map}: transforms the success value
569
647
  * with `f`. `f` is synchronous; a throw becomes a `Defect`. An async callback
@@ -611,32 +689,22 @@ type AsyncResultMethods<T, E> = {
611
689
  /** Asynchronous {@link ResultMethods.discard | discard}: drops the value, collapsing the success type to `void`. */
612
690
  discard(): AsyncResult$1<void, E>;
613
691
  /**
614
- * Asynchronous {@link ResultMethods.mapErr | mapErr}. `f` is synchronous; a
615
- * throw becomes a `Defect`. An async callback is rejected at compile time
616
- * ({@link NotThenable}).
617
- */
618
- mapErr<E2>(f: (error: E) => E2 & NotThenable<E2>): AsyncResult$1<T, E2>;
619
- /**
620
- * Asynchronous {@link ResultMethods.flatMapErr | flatMapErr}. `f` may return a
621
- * `Result` or an `AsyncResult`.
692
+ * Asynchronous {@link ResultMethods.mapErr | mapErr} — the same exhaustive
693
+ * {@link ErrMatcher} form; the combinator calls `.exhaustive()`.
622
694
  */
623
- flatMapErr<U, E2>(f: (error: E) => Result$1<U, E2> | AsyncResult$1<U, E2>): AsyncResult$1<T | U, E2>;
695
+ mapErr<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): AsyncResult$1<T, MatchErrOut<M>>;
624
696
  /**
625
- * @deprecated Renamed to {@link AsyncResultMethods.flatMapErr | flatMapErr}.
626
- * This alias will be removed in a future major.
697
+ * Asynchronous {@link ResultMethods.flatMapErr | flatMapErr} — the same
698
+ * exhaustive {@link ErrMatcher} form. Unlike the sync form, a branch may
699
+ * return a `Result` **or** an `AsyncResult`.
627
700
  */
628
- orElse<U, E2>(f: (error: E) => Result$1<U, E2> | AsyncResult$1<U, E2>): AsyncResult$1<T | U, E2>;
701
+ flatMapErr<M extends ExhaustiveMatch<Result$1<unknown, unknown> | AsyncResult$1<unknown, unknown> | Defect>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): AsyncResult$1<T | OkOf<MatchOut<M>> | AsyncOkOf<MatchOut<M>>, ErrOf<MatchOut<M>> | AsyncErrOf<MatchOut<M>>>;
629
702
  /**
630
- * Asynchronous {@link ResultMethods.recoverErr | recoverErr}. `f` is
631
- * synchronous; a throw becomes a `Defect`. An async callback is rejected at
632
- * compile time ({@link NotThenable}).
633
- */
634
- recoverErr<U>(f: (error: E) => U & NotThenable<U>): AsyncResult$1<T | U, never>;
635
- /**
636
- * @deprecated Renamed to {@link AsyncResultMethods.recoverErr | recoverErr}.
637
- * This alias will be removed in a future major.
703
+ * Asynchronous {@link ResultMethods.recoverErr | recoverErr} — the same
704
+ * exhaustive {@link ErrMatcher} form. Branches are synchronous; a throw
705
+ * becomes a `Defect`.
638
706
  */
639
- recover<U>(f: (error: E) => U & NotThenable<U>): AsyncResult$1<T | U, never>;
707
+ recoverErr<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): AsyncResult$1<T | MatchErrOut<M>, never>;
640
708
  /**
641
709
  * Asynchronous {@link ResultMethods.tapErr | tapErr}. `f` is synchronous; if it
642
710
  * throws, the result is a `Defect` whose cause is an `AggregateError` of
@@ -646,7 +714,7 @@ type AsyncResultMethods<T, E> = {
646
714
  * too — a failable effect belongs in
647
715
  * {@link AsyncResultMethods.flatTapErr | flatTapErr}.
648
716
  */
649
- tapErr<R>(f: (error: E) => R & NotThenable<R>): AsyncResult$1<T, E>;
717
+ tapErr(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<unknown>): AsyncResult$1<T, E>;
650
718
  /**
651
719
  * Asynchronous {@link ResultMethods.flatTapErr | flatTapErr} — the
652
720
  * error-channel mirror of `flatTap`. `f` may return a `Result` **or** an
@@ -655,7 +723,7 @@ type AsyncResultMethods<T, E> = {
655
723
  * an `AggregateError` of `[thrown, original failure]` — observing a failure
656
724
  * never destroys it.
657
725
  */
658
- flatTapErr<E2>(f: (error: E) => Result$1<unknown, E2> | AsyncResult$1<unknown, E2>): AsyncResult$1<T, E | E2>;
726
+ flatTapErr<E2>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<Result$1<unknown, E2> | AsyncResult$1<unknown, E2>>): AsyncResult$1<T, E | E2>;
659
727
  /**
660
728
  * Asynchronous {@link ResultMethods.recoverDefect | recoverDefect}. `f` may
661
729
  * return a `Result` or an `AsyncResult`.
@@ -669,50 +737,40 @@ type AsyncResultMethods<T, E> = {
669
737
  */
670
738
  tapDefect<R>(f: (cause: unknown) => R & NotThenable<R>): AsyncResult$1<T, E>;
671
739
  /**
672
- * Asynchronous {@link ResultMethods.match | match}. Handlers are synchronous;
673
- * resolves to a `Promise<R>`.
740
+ * Asynchronous {@link ResultMethods.tapFailure | tapFailure} — the
741
+ * cross-channel observer. `f` receives the narrowed failure variant
742
+ * ({@link FailureView}); if it throws, the result is a `Defect` whose cause
743
+ * is an `AggregateError` of `[thrown, original failure]` — observing a
744
+ * failure never destroys it. An async callback is rejected at compile time
745
+ * ({@link NotThenable}).
674
746
  */
675
- match<R>(cases: {
676
- ok: (value: T) => R;
677
- err: (error: E) => R;
678
- defect: (cause: unknown) => R;
679
- }): Promise<R>;
747
+ tapFailure<R>(f: (failure: FailureView<E, T>) => R & NotThenable<R>): AsyncResult$1<T, E>;
748
+ /**
749
+ * Asynchronous {@link ResultMethods.match | match}. Handlers are synchronous
750
+ * (the `err` handler returns an exhaustive {@link ErrMatcher} builder, no
751
+ * `defect` helper); resolves to a `Promise` of the folded value.
752
+ */
753
+ match<ROk, RDefect, M extends ExhaustiveMatch<unknown>>(cases: {
754
+ ok: (value: T) => ROk;
755
+ err: (matcher: ErrMatcher<E>) => M;
756
+ defect: (cause: unknown) => RDefect;
757
+ }): Promise<ROk | RDefect | MatchOut<M>>;
680
758
  /**
681
759
  * Asynchronous {@link ResultMethods.get | get}. Compiles only when the
682
760
  * error channel is empty (`this: AsyncResult<T, never>`); the returned promise
683
761
  * rejects on a `Defect` (rethrowing its cause).
684
762
  */
685
763
  get(this: AsyncResult$1<T, never>): Promise<T>;
686
- /**
687
- * @deprecated Renamed to {@link AsyncResultMethods.get | get}. This alias will
688
- * be removed in a future major.
689
- */
690
- unwrap(this: AsyncResult$1<T, never>): Promise<T>;
691
764
  /**
692
765
  * Asynchronous {@link ResultMethods.getErr | getErr}. Compiles only when
693
766
  * the success channel is empty (`this: AsyncResult<never, E>`); the returned
694
767
  * promise rejects on a `Defect` (rethrowing its cause).
695
768
  */
696
769
  getErr(this: AsyncResult$1<never, E>): Promise<E>;
697
- /**
698
- * @deprecated Renamed to {@link AsyncResultMethods.getErr | getErr}. This alias
699
- * will be removed in a future major.
700
- */
701
- unwrapErr(this: AsyncResult$1<never, E>): Promise<E>;
702
770
  /** Asynchronous {@link ResultMethods.getOr | getOr}. */
703
771
  getOr<U>(fallback: U): Promise<T | U>;
704
- /**
705
- * @deprecated Renamed to {@link AsyncResultMethods.getOr | getOr}. This alias
706
- * will be removed in a future major.
707
- */
708
- unwrapOr<U>(fallback: U): Promise<T | U>;
709
772
  /** Asynchronous {@link ResultMethods.getOrElse | getOrElse}. */
710
773
  getOrElse<U>(f: (error: E) => U): Promise<T | U>;
711
- /**
712
- * @deprecated Renamed to {@link AsyncResultMethods.getOrElse | getOrElse}. This
713
- * alias will be removed in a future major.
714
- */
715
- unwrapOrElse<U>(f: (error: E) => U): Promise<T | U>;
716
774
  /** Asynchronous {@link ResultMethods.getOrNull | getOrNull}. */
717
775
  getOrNull(): Promise<T | null>;
718
776
  /** Asynchronous {@link ResultMethods.getOrUndefined | getOrUndefined}. */
@@ -720,9 +778,10 @@ type AsyncResultMethods<T, E> = {
720
778
  /**
721
779
  * Asynchronous {@link ResultMethods.getOrThrow | getOrThrow} — the returned
722
780
  * promise **rejects** with the modeled error on `Err` (or the original cause
723
- * on a `Defect`), rather than throwing synchronously.
781
+ * on a `Defect`), rather than throwing synchronously. Gated the same way: it
782
+ * compiles only when the error channel is non-empty (`E` is not `never`).
724
783
  */
725
- getOrThrow(): Promise<T>;
784
+ getOrThrow(this: [E] extends [never] ? never : AsyncResult$1<T, E>): Promise<T>;
726
785
  };
727
786
  /**
728
787
  * The asynchronous counterpart of {@link Result}: an awaitable wrapper carrying
@@ -795,7 +854,7 @@ type ErrOf<R> = R extends {
795
854
  *
796
855
  * @category Types
797
856
  */
798
- type AsyncOkOf<R> = R extends AsyncResult$1<infer T, unknown> ? T : never;
857
+ type AsyncOkOf<R> = R extends Awaitable<infer Res> ? OkOf<Res> : never;
799
858
  /**
800
859
  * Extract the error type `E` from an {@link AsyncResult} type — the async
801
860
  * counterpart of {@link ErrOf}.
@@ -809,7 +868,7 @@ type AsyncOkOf<R> = R extends AsyncResult$1<infer T, unknown> ? T : never;
809
868
  *
810
869
  * @category Types
811
870
  */
812
- type AsyncErrOf<R> = R extends AsyncResult$1<unknown, infer E> ? E : never;
871
+ type AsyncErrOf<R> = R extends Awaitable<infer Res> ? ErrOf<Res> : never;
813
872
  //#endregion
814
873
  //#region src/constructors.d.ts
815
874
  /**
@@ -996,12 +1055,12 @@ declare function isDefect<T, E>(r: Result$1<T, E>): r is DefectView<T, E>;
996
1055
  * `Ok`.
997
1056
  *
998
1057
  * @remarks
999
- * The offending value is exposed two ways: the typed {@link UnwrapError.error}
1058
+ * The offending value is exposed two ways: the typed {@link GetError.error}
1000
1059
  * property for programmatic access, and the standard `Error.cause` for the
1001
1060
  * runtime and devtools to chain — when `E` is an `Error` (e.g. a `TaggedError`)
1002
1061
  * its original stack is printed under "caused by".
1003
1062
  *
1004
- * A `Defect` is never wrapped in an `UnwrapError`: its original cause is
1063
+ * A `Defect` is never wrapped in a `GetError`: its original cause is
1005
1064
  * re-thrown (with its original stack) instead.
1006
1065
  *
1007
1066
  * `get()` and `getErr()` are type-gated (`this: Result<T, never>` /
@@ -1009,11 +1068,11 @@ declare function isDefect<T, E>(r: Result$1<T, E>): r is DefectView<T, E>;
1009
1068
  * unreachable through well-typed code — it remains only as a defensive guard
1010
1069
  * against unsound runtime misuse (e.g. an `as` cast past the gate).
1011
1070
  *
1012
- * @typeParam E - the type of the {@link UnwrapError.error} it carries.
1071
+ * @typeParam E - the type of the {@link GetError.error} it carries.
1013
1072
  *
1014
1073
  * @category Errors
1015
1074
  */
1016
- declare class UnwrapError<E = unknown> extends Error {
1075
+ declare class GetError<E = unknown> extends Error {
1017
1076
  /**
1018
1077
  * The offending value: the `Err` error for `get()`, or the `Ok` value for
1019
1078
  * `getErr()`.
@@ -1092,27 +1151,6 @@ declare function isResult(x: unknown): x is Result$1<unknown, unknown>;
1092
1151
  */
1093
1152
  declare function Do(): Result$1<{}, never>;
1094
1153
  //#endregion
1095
- //#region src/defect.d.ts
1096
- declare const DEFECT: unique symbol;
1097
- /**
1098
- * The opaque marker a `qualify` function returns to triage a cause as
1099
- * **unexpected**.
1100
- *
1101
- * @remarks
1102
- * `qualify` (passed to {@link fromPromise} / {@link fromThrowable}) returns
1103
- * `E | Defect`: either a modeled domain error, or a `Defect` produced by the
1104
- * injected `defect` helper to say "this failure is not modeled". A `Defect` is
1105
- * opaque — it carries the original cause for the boundary to convert into the
1106
- * third runtime state of a `Result`. It is **not** a public value; the only way
1107
- * to mint one is the `defect` helper the boundary passes to `qualify`.
1108
- *
1109
- * @internal
1110
- */
1111
- type Defect = {
1112
- readonly [DEFECT]: true;
1113
- readonly cause: unknown;
1114
- };
1115
- //#endregion
1116
1154
  //#region src/interop.d.ts
1117
1155
  /**
1118
1156
  * Bridge a nullable value into a {@link Result}: absence becomes a **modeled**
@@ -1547,8 +1585,10 @@ type TaggedErrorConstructor<Tag extends string> = {
1547
1585
  * rejected at compile time (and excluded from the instance type), so it can't
1548
1586
  * shadow `Error.name`.
1549
1587
  *
1550
- * `_tag` is the discriminant used by {@link matchTags}; `Error.name` is the
1551
- * human-facing label in stack traces and logs. By default they coincide, but
1588
+ * `_tag` is the discriminant matched by {@link tag} in the error combinators
1589
+ * (`result.mapErr((matcher) => matcher.with(tag("NotFound"), …))`) and in
1590
+ * `match`; `Error.name` is the human-facing label in stack traces and logs. By
1591
+ * default they coincide, but
1552
1592
  * they can be **decoupled** with `options.name` — so a tag can be namespaced for
1553
1593
  * collision-safety (`"@my-lib/RetryableError"`) without that slash-prefixed
1554
1594
  * string leaking into `Error.name`:
@@ -1586,78 +1626,28 @@ declare function TaggedError<Tag extends string>(tag: Tag, options?: {
1586
1626
  readonly name?: string;
1587
1627
  }): TaggedErrorConstructor<Tag>;
1588
1628
  /**
1589
- * The handler object {@link matchTags} requires: a branch per error tag, plus
1590
- * `Ok` and `Defect`. Miss a tag and it will not compile — the exhaustiveness is
1591
- * enforced by the type, with no `.exhaustive()` to forget.
1592
- *
1593
- * @typeParam T - the success value type.
1594
- * @typeParam E - the tagged error union.
1595
- * @typeParam R - the folded result type.
1596
- *
1597
- * @category Types
1598
- */
1599
- type TagHandlers<T, E extends {
1600
- _tag: string;
1601
- }, R> = {
1602
- Ok: (value: T) => R;
1603
- Defect: (cause: unknown) => R;
1604
- } & { [K in E["_tag"]]: (error: Extract<E, {
1605
- _tag: K;
1606
- }>) => R; };
1607
- /**
1608
- * The channel-handler names are reserved: an error tag named `"Ok"` or
1609
- * `"Defect"` would collide with them inside {@link TagHandlers}, so
1610
- * {@link matchTags} rejects such unions at the call site.
1611
- *
1612
- * @internal
1613
- */
1614
- type ReservedTagError = 'unthrown: error tags "Ok" and "Defect" are reserved by matchTags — rename the colliding tag (TaggedError\'s options.name can keep the display name)';
1615
- /**
1616
- * Exhaustively fold a {@link Result} (or {@link AsyncResult}) whose error type is
1617
- * a tagged union, dispatching each error to the handler matching its `_tag`.
1618
- *
1619
- * @remarks
1620
- * The `handlers` object must provide `Ok`, `Defect`, and exactly one function
1621
- * per error tag; each tag's handler receives the narrowed error variant. A
1622
- * missing tag is a compile error. For an `AsyncResult`, the fold resolves to a
1623
- * `Promise<R>`. At runtime, an error whose `_tag` has no handler (possible only
1624
- * outside the typed contract) is routed to the `Defect` handler — an unmodeled
1625
- * tag is an unmodeled failure. Tags named `"Ok"` or `"Defect"` are rejected at
1626
- * compile time.
1629
+ * A `ts-pattern` pattern matching any value whose `_tag` equals `value` — a
1630
+ * {@link TaggedError}, or any discriminated member. Equivalent to the object
1631
+ * pattern `{ _tag: value }`, but reads better inside an error-matching
1632
+ * combinator and narrows to the matching variant, payload included.
1627
1633
  *
1628
- * @typeParam T - the success value type.
1629
- * @typeParam E - the tagged error union (`E extends { _tag: string }`).
1630
- * @typeParam R - the folded result type.
1631
- * @param result - the result to fold.
1632
- * @param handlers - one branch per channel/tag.
1634
+ * @typeParam Tag - the string literal tag to match.
1635
+ * @param value - the `_tag` to match.
1633
1636
  *
1634
1637
  * @category Tagged errors
1635
1638
  *
1636
1639
  * @example
1637
1640
  * ```ts
1638
- * import { Ok, Err, matchTags, TaggedError, type Result } from "unthrown";
1639
- *
1640
- * class NotFound extends TaggedError("NotFound") {}
1641
- * class Forbidden extends TaggedError("Forbidden")<{ user: string }> {}
1642
- *
1643
- * const fold = (r: Result<number, NotFound | Forbidden>) =>
1644
- * matchTags(r, {
1645
- * Ok: (n) => `got ${n}`,
1646
- * Defect: (cause) => `bug: ${String(cause)}`,
1647
- * NotFound: () => "404",
1648
- * Forbidden: (e) => `403 for ${e.user}`,
1649
- * });
1650
- *
1651
- * fold(Ok(1)); // => "got 1"
1652
- * fold(Err(new Forbidden({ user: "ada" }))); // => "403 for ada"
1641
+ * result.mapErr((matcher) =>
1642
+ * matcher
1643
+ * .with(tag("NotFound"), () => new NotFoundException())
1644
+ * .with(tag("Conflict"), (e) => new ConflictException(e.key)),
1645
+ * );
1653
1646
  * ```
1654
1647
  */
1655
- declare function matchTags<T, E extends {
1656
- _tag: string;
1657
- }, R>(result: Result$1<T, E>, handlers: TagHandlers<T, E, R> & ([Extract<E["_tag"], "Ok" | "Defect">] extends [never] ? unknown : ReservedTagError)): R;
1658
- declare function matchTags<T, E extends {
1659
- _tag: string;
1660
- }, R>(result: AsyncResult$1<T, E>, handlers: TagHandlers<T, E, R> & ([Extract<E["_tag"], "Ok" | "Defect">] extends [never] ? unknown : ReservedTagError)): Promise<R>;
1648
+ declare function tag<const Tag extends string>(value: Tag): {
1649
+ _tag: Tag;
1650
+ };
1661
1651
  //#endregion
1662
- export { type AsyncErrOf, type AsyncOkOf, AsyncResult, type AsyncResultMethods, type Awaitable, type DefectView, Do, Err, ErrAsync, type ErrOf, type ErrView, type NotThenable, Ok, OkAsync, type OkOf, type OkView, Result, type ResultMethods, type TagHandlers, TaggedError, type TaggedErrorConstructor, type TaggedErrorInstance, UnwrapError, all, allAsync, allFromDict, allFromDictAsync, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, matchTags };
1652
+ export { type AsyncErrOf, type AsyncOkOf, AsyncResult, type AsyncResultMethods, type Awaitable, type DefectView, Do, Err, ErrAsync, type ErrMatcher, type ErrOf, type ErrView, type FailureView, GetError, type NotThenable, Ok, OkAsync, type OkOf, type OkView, P, Result, type ResultMethods, TaggedError, type TaggedErrorConstructor, type TaggedErrorInstance, all, allAsync, allFromDict, allFromDictAsync, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, match, tag };
1663
1653
  //# sourceMappingURL=index.d.cts.map