unthrown 5.9.0 → 5.10.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 +0 -3
- package/dist/index.cjs +268 -60
- package/dist/index.d.cts +1768 -1624
- package/dist/index.d.mts +1768 -1624
- package/dist/index.mjs +268 -60
- package/package.json +6 -6
package/dist/index.d.cts
CHANGED
|
@@ -63,26 +63,53 @@ type UniversalPattern = PatternMatcher<unknown> & {
|
|
|
63
63
|
* @internal
|
|
64
64
|
*/
|
|
65
65
|
type MatchedOf<Pt> = Pt extends PatternMatcher<infer M> ? M : Pt extends object ? { [K in keyof Pt]: MatchedOf<Pt[K]>; } : Pt;
|
|
66
|
+
/**
|
|
67
|
+
* Reject a **keyless object pattern** (`{}`) where it is written.
|
|
68
|
+
*
|
|
69
|
+
* @remarks
|
|
70
|
+
* At runtime an empty object pattern matches *every* object (no key to
|
|
71
|
+
* disagree on), and at the type level `MatchedOf<{}>` is `{}`, so `Exclude`
|
|
72
|
+
* removes every non-nullish case — an unflagged catch-all, invisible to
|
|
73
|
+
* `@unthrown/oxlint`'s `no-catch-all-pattern`. Mapping it to a branded string
|
|
74
|
+
* makes the argument unassignable, with the reason in the error. `null` /
|
|
75
|
+
* `undefined` (also keyless) stay legal literal patterns, and every `P.*`
|
|
76
|
+
* pattern carries a symbol key. The check recurses into structural patterns:
|
|
77
|
+
* a nested `{ data: {} }` is rejected too, since the type level treats `{}` as
|
|
78
|
+
* matching a primitive `data` while the runtime rejects a non-object there — a
|
|
79
|
+
* match that compiles as exhaustive and then throws.
|
|
80
|
+
*
|
|
81
|
+
* @internal
|
|
82
|
+
*/
|
|
83
|
+
type NoEmptyPattern<Pt> = [Pt] extends [null | undefined] ? Pt : [Pt] extends [PatternMatcher<unknown>] ? Pt : [keyof Pt] extends [never] ? "unthrown: an empty object pattern `{}` matches every object — name the case (a key to match on), or use P._ deliberately" : Pt extends object ? { [K in keyof Pt]: NoEmptyPattern<Pt[K]>; } : Pt;
|
|
66
84
|
/**
|
|
67
85
|
* The diagnostic type of `.exhaustive` on a builder that has NOT covered every
|
|
68
86
|
* case: not callable (so it fails the `ExhaustiveMatch` constraint at the call
|
|
69
87
|
* site), and it names the remaining cases so the error reads as a to-do list.
|
|
88
|
+
* The alias NAME is the diagnostic: the compiler prints "Type
|
|
89
|
+
* 'UnhandledCases<NotFound>' provides no match for the signature …", so it
|
|
90
|
+
* says what is wrong and lists what is left (it was `NonExhaustive<…>`).
|
|
70
91
|
*
|
|
71
92
|
* @internal
|
|
72
93
|
*/
|
|
73
|
-
type
|
|
94
|
+
type UnhandledCases<Remaining> = {
|
|
74
95
|
readonly "unthrown: this match is not exhaustive — add a `.with(…)` for the remaining cases": Remaining;
|
|
75
96
|
};
|
|
76
97
|
/**
|
|
77
98
|
* The "no output type declared" sentinel for a builder's `Declared` parameter.
|
|
78
|
-
*
|
|
79
|
-
*
|
|
99
|
+
*
|
|
100
|
+
* @remarks
|
|
101
|
+
* A string-keyed brand, **not** a `unique symbol`: every unpinned builder's
|
|
102
|
+
* type carries it (`Matcher<E, R, O, Unset>`), so a consumer's
|
|
103
|
+
* `export const m = match(x).with(…)` has to be able to print it under
|
|
104
|
+
* declaration emit. A non-exported `unique symbol` cannot be named there
|
|
105
|
+
* (TS2527), while an object literal type can always be written out. No user
|
|
106
|
+
* type collides with it in practice — the key is the explanation.
|
|
80
107
|
*
|
|
81
108
|
* @internal
|
|
82
109
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
110
|
+
type Unset = {
|
|
111
|
+
readonly "unthrown: no output type declared — call .returnType<R>() to pin one": true;
|
|
112
|
+
};
|
|
86
113
|
/**
|
|
87
114
|
* A branch handler's return position: free inference (`O2`) while the builder
|
|
88
115
|
* is unpinned — today's behaviour, unchanged — or the declared type once
|
|
@@ -148,7 +175,7 @@ type Matcher<E, Remaining, O, Declared = Unset> = {
|
|
|
148
175
|
* `Remaining`, so cases already handled by earlier arms are excluded); the
|
|
149
176
|
* matched cases are subtracted from `Remaining`.
|
|
150
177
|
*/
|
|
151
|
-
with<const Pts extends readonly [unknown, ...unknown[]], O2>(...args: [...patterns: Pts, handler: (value: Extract<Remaining, MatchedOf<Pts[number]>>) => BranchReturn<Declared, O2>]): Matcher<E, Exclude<Remaining, MatchedOf<Pts[number]>>, O | O2, Declared>;
|
|
178
|
+
with<const Pts extends readonly [unknown, ...unknown[]], O2>(...args: [...patterns: { [I in keyof Pts]: NoEmptyPattern<Pts[I]>; }, handler: (value: Extract<Remaining, MatchedOf<Pts[number]>>) => BranchReturn<Declared, O2>]): Matcher<E, Exclude<Remaining, MatchedOf<Pts[number]>>, O | O2, Declared>;
|
|
152
179
|
/**
|
|
153
180
|
* Declare the match's output type up front: every subsequent branch handler
|
|
154
181
|
* is checked against `R`, and the match evaluates to `R` instead of the
|
|
@@ -183,7 +210,7 @@ type Matcher<E, Remaining, O, Declared = Unset> = {
|
|
|
183
210
|
* naming the remaining cases, and the builder fails the `ExhaustiveMatch`
|
|
184
211
|
* constraint at the combinator call site.
|
|
185
212
|
*/
|
|
186
|
-
exhaustive: [Remaining] extends [never] ? () => PinnedOut<Declared, O> :
|
|
213
|
+
exhaustive: [Remaining] extends [never] ? () => PinnedOut<Declared, O> : UnhandledCases<Remaining>;
|
|
187
214
|
/**
|
|
188
215
|
* Execute the match (the combinators call this; it runs `.exhaustive()`).
|
|
189
216
|
* A value with no matching arm throws {@link NonExhaustiveError} —
|
|
@@ -200,8 +227,25 @@ type Matcher<E, Remaining, O, Declared = Unset> = {
|
|
|
200
227
|
* is a bug).
|
|
201
228
|
*
|
|
202
229
|
* @category Errors
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
* ```ts
|
|
233
|
+
* import { match, NonExhaustiveError } from "unthrown";
|
|
234
|
+
*
|
|
235
|
+
* // A value typed "a" | "b" that is really "c" (a cast, a raw-JS caller):
|
|
236
|
+
* const rogue = "c" as "a" | "b";
|
|
237
|
+
* try {
|
|
238
|
+
* match(rogue)
|
|
239
|
+
* .with("a", () => 1)
|
|
240
|
+
* .with("b", () => 2)
|
|
241
|
+
* .exhaustive();
|
|
242
|
+
* } catch (error) {
|
|
243
|
+
* error instanceof NonExhaustiveError; // => true
|
|
244
|
+
* (error as NonExhaustiveError).input; // => "c"
|
|
245
|
+
* }
|
|
246
|
+
* ```
|
|
203
247
|
*/
|
|
204
|
-
declare class NonExhaustiveError extends Error {
|
|
248
|
+
export declare class NonExhaustiveError extends Error {
|
|
205
249
|
/** The value no arm matched. */
|
|
206
250
|
readonly input: unknown;
|
|
207
251
|
constructor(input: unknown);
|
|
@@ -220,8 +264,26 @@ declare class NonExhaustiveError extends Error {
|
|
|
220
264
|
* {@link P}).
|
|
221
265
|
*
|
|
222
266
|
* @category Constructors
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* import { match, type Result } from "unthrown";
|
|
271
|
+
*
|
|
272
|
+
* // Matching a whole Result natively — every variant named, `.exhaustive()` last:
|
|
273
|
+
* declare const r: Result<number, "odd" | "negative">;
|
|
274
|
+
* const label = match(r)
|
|
275
|
+
* .with({ tag: "Ok" }, (ok) => `got ${ok.value}`)
|
|
276
|
+
* .with({ tag: "Err" }, (err) => `failed: ${err.error}`)
|
|
277
|
+
* .with({ tag: "Defect" }, () => "bug")
|
|
278
|
+
* .exhaustive();
|
|
279
|
+
*
|
|
280
|
+
* // Inside a combinator, return the un-terminated builder — it runs `.exhaustive()`:
|
|
281
|
+
* const reason = r.mapErrCases((matcher) =>
|
|
282
|
+
* matcher.with("odd", () => "not even" as const).with("negative", () => "below zero" as const),
|
|
283
|
+
* ); // Result<number, "not even" | "below zero">
|
|
284
|
+
* ```
|
|
223
285
|
*/
|
|
224
|
-
declare function match<const E>(value: E): Matcher<E, E, never>;
|
|
286
|
+
export declare function match<const E>(value: E): Matcher<E, E, never>;
|
|
225
287
|
/**
|
|
226
288
|
* The pattern namespace (unthrown's own; the former ts-pattern `P`):
|
|
227
289
|
*
|
|
@@ -246,1932 +308,2014 @@ declare function match<const E>(value: E): Matcher<E, E, never>;
|
|
|
246
308
|
* (`.with(P.tag("A"), P.tag("B"), handler)`).
|
|
247
309
|
* - `P.instanceOf(Cls)` — an `instanceof` check, narrowing to the class
|
|
248
310
|
* instance type (for union members that are not tagged, e.g. a third-party
|
|
249
|
-
* error class).
|
|
311
|
+
* error class). **Exhaustiveness here is structural, the check is not:**
|
|
312
|
+
* two classes with the same shape (`class A extends Error {}`,
|
|
313
|
+
* `class B extends Error {}`) are one type to the compiler, so a match
|
|
314
|
+
* naming only `A` compiles as exhaustive while a `B` fails `instanceof A`
|
|
315
|
+
* at runtime and becomes a `Defect`. Give each class a distinguishing field
|
|
316
|
+
* (a `readonly kind = "A"` literal) or use `TaggedError`, and the missing
|
|
317
|
+
* arm is a compile error again.
|
|
250
318
|
* - `P.when(guard)` — an arbitrary type-guard predicate. Also the way to match
|
|
251
319
|
* a primitive shape (`P.when((v): v is string => typeof v === "string")`),
|
|
252
320
|
* and grouping patterns under one handler is what a `.with(a, b, handler)`
|
|
253
321
|
* arm already does.
|
|
254
322
|
*
|
|
255
323
|
* @category Constructors
|
|
324
|
+
*
|
|
325
|
+
* @example
|
|
326
|
+
* ```ts
|
|
327
|
+
* import { P, TaggedError, type Result } from "unthrown";
|
|
328
|
+
*
|
|
329
|
+
* class NotFound extends TaggedError("NotFound")<{ id: string }> {}
|
|
330
|
+
* class Conflict extends TaggedError("Conflict") {}
|
|
331
|
+
* class VendorTimeout extends Error {
|
|
332
|
+
* readonly afterMs = 30_000;
|
|
333
|
+
* }
|
|
334
|
+
*
|
|
335
|
+
* declare const r: Result<string, NotFound | Conflict | VendorTimeout | "rate_limited">;
|
|
336
|
+
* const status = r.match({
|
|
337
|
+
* ok: () => 200,
|
|
338
|
+
* errCases: (matcher) =>
|
|
339
|
+
* matcher
|
|
340
|
+
* .with(P.tag("NotFound"), () => 404) // a TaggedError, narrowed with its payload
|
|
341
|
+
* .with(P.tag("Conflict"), () => 409)
|
|
342
|
+
* .with(P.instanceOf(VendorTimeout), (e) => (e.afterMs > 10_000 ? 504 : 503))
|
|
343
|
+
* .with(
|
|
344
|
+
* P.when((v): v is "rate_limited" => v === "rate_limited"),
|
|
345
|
+
* () => 429,
|
|
346
|
+
* ),
|
|
347
|
+
* defect: () => 500,
|
|
348
|
+
* });
|
|
349
|
+
* ```
|
|
256
350
|
*/
|
|
257
|
-
declare const P: Readonly<{
|
|
351
|
+
export declare const P: Readonly<{
|
|
258
352
|
_: UniversalPattern;
|
|
259
353
|
tag: <const Tag extends string>(value: Tag) => {
|
|
260
|
-
_tag: Tag;
|
|
354
|
+
readonly _tag: Tag;
|
|
261
355
|
};
|
|
262
356
|
instanceOf: <C extends abstract new (...args: never[]) => unknown>(cls: C) => PatternMatcher<InstanceType<C>>;
|
|
263
357
|
when: <G>(guard: (value: unknown) => value is G) => PatternMatcher<G>;
|
|
264
358
|
}>;
|
|
265
359
|
//#endregion
|
|
266
|
-
//#region src/
|
|
360
|
+
//#region src/core.d.ts
|
|
267
361
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
362
|
+
* Thrown by a {@link Result}'s `get` / `getErr` when the assertion is
|
|
363
|
+
* wrong on a *modeled* result — `get()` on an `Err`, or `getErr()` on an
|
|
364
|
+
* `Ok`.
|
|
270
365
|
*
|
|
271
|
-
* @
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
*
|
|
276
|
-
* property of type `U`). `Omit<T, K>` first drops any existing `K`, so re-binding
|
|
277
|
-
* a name **overwrites** it — matching the runtime spread — rather than producing
|
|
278
|
-
* an unsound `T[K] & U` intersection.
|
|
366
|
+
* @remarks
|
|
367
|
+
* The offending value is exposed two ways: the typed {@link GetError.error}
|
|
368
|
+
* property for programmatic access, and the standard `Error.cause` for the
|
|
369
|
+
* runtime and devtools to chain — when `E` is an `Error` (e.g. a `TaggedError`)
|
|
370
|
+
* its original stack is printed under "caused by".
|
|
279
371
|
*
|
|
280
|
-
*
|
|
372
|
+
* A `Defect` is never wrapped in a `GetError`: its original cause is
|
|
373
|
+
* re-thrown (with its original stack) instead.
|
|
374
|
+
*
|
|
375
|
+
* `get()` and `getErr()` are type-gated (`this: Result<T, never>` /
|
|
376
|
+
* `Result<never, E>`), so the wrong-variant branch that throws this is
|
|
377
|
+
* unreachable through well-typed code — it remains only as a defensive guard
|
|
378
|
+
* against unsound runtime misuse (e.g. an `as` cast past the gate).
|
|
379
|
+
*
|
|
380
|
+
* @typeParam E - the type of the {@link GetError.error} it carries.
|
|
381
|
+
*
|
|
382
|
+
* @category Errors
|
|
281
383
|
*/
|
|
282
|
-
|
|
384
|
+
export declare class GetError<E = unknown> extends Error {
|
|
385
|
+
/**
|
|
386
|
+
* The offending value: the `Err` error for `get()`, or the `Ok` value for
|
|
387
|
+
* `getErr()`.
|
|
388
|
+
*/
|
|
389
|
+
readonly error: E;
|
|
390
|
+
constructor(error: E);
|
|
391
|
+
}
|
|
283
392
|
/**
|
|
284
|
-
*
|
|
285
|
-
* enforcement of "combinator callbacks are synchronous" (see the
|
|
286
|
-
* {@link AsyncResult} remarks).
|
|
393
|
+
* Type guard: is `x` a {@link Result} (any of `Ok` / `Err` / `Defect`)?
|
|
287
394
|
*
|
|
288
395
|
* @remarks
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
*
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
396
|
+
* Unlike {@link isOk} / {@link isErr} / {@link isDefect}, which narrow a value
|
|
397
|
+
* already known to be a `Result`, this narrows from `unknown` — useful at an
|
|
398
|
+
* untyped boundary. It checks the value carries the `Result` prototype
|
|
399
|
+
* (`instanceof` first, falling back to the `Symbol.for("unthrown.Result")`
|
|
400
|
+
* brand the prototype carries — so a `Result` built by **another copy** of
|
|
401
|
+
* unthrown, e.g. the CJS and ESM builds loaded side by side, is still
|
|
402
|
+
* recognised). A look-alike plain object (`{ tag: "Ok" }`) carries neither and
|
|
403
|
+
* is **not** matched; nor is a forgery built on the real prototype whose `tag` or
|
|
404
|
+
* payload is a getter (both must be own data properties). An `AsyncResult` is not a `Result` and returns `false`.
|
|
295
405
|
*
|
|
296
|
-
*
|
|
297
|
-
* fires when only SOME arms of a union return are thenable — a *sometimes*-async
|
|
298
|
-
* callback (`flag ? 1 : work()`) is still an unawaited effect whose rejection
|
|
299
|
-
* the pipeline never sees. The tuple-wrapped form is false for a partial union
|
|
300
|
-
* and let exactly that through. This is the same reasoning `fromPromise`'s
|
|
301
|
-
* async-qualify guard already used.
|
|
406
|
+
* @returns `true` when `x` is a `Result` produced by this library.
|
|
302
407
|
*
|
|
303
|
-
* @
|
|
304
|
-
*
|
|
408
|
+
* @example
|
|
409
|
+
* ```ts
|
|
410
|
+
* import { isResult, Ok, P } from "unthrown";
|
|
411
|
+
*
|
|
412
|
+
* isResult(Ok(1)); // => true
|
|
413
|
+
* isResult({ tag: "Ok" }); // => false (look-alike, wrong prototype)
|
|
414
|
+
* isResult(Ok(1).toAsync()); // => false (an AsyncResult is not a Result)
|
|
415
|
+
*
|
|
416
|
+
* const x: unknown = Ok(1);
|
|
417
|
+
* if (isResult(x))
|
|
418
|
+
* // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
|
|
419
|
+
* // so the `P._` escape hatch is the only arm that can terminate the match:
|
|
420
|
+
* // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
|
|
421
|
+
* x.match({
|
|
422
|
+
* ok: () => 1,
|
|
423
|
+
* errCases: (m) => m.with(P._, () => 0),
|
|
424
|
+
* defect: () => -1,
|
|
425
|
+
* });
|
|
426
|
+
* ```
|
|
427
|
+
*
|
|
428
|
+
* @category Guards
|
|
305
429
|
*/
|
|
306
|
-
|
|
430
|
+
export declare function isResult(x: unknown): x is Result<unknown, unknown>;
|
|
431
|
+
//#endregion
|
|
432
|
+
//#region src/do.d.ts
|
|
307
433
|
/**
|
|
308
|
-
*
|
|
309
|
-
* `
|
|
310
|
-
* `.with(pattern, handler)` on it; the combinator itself calls `.exhaustive()`,
|
|
311
|
-
* so the callback returns the **un-terminated** builder.
|
|
434
|
+
* Start a do-notation chain with an empty object scope, grown step by step with
|
|
435
|
+
* `bind` (for `Result`-returning steps) and `let` (for pure values).
|
|
312
436
|
*
|
|
313
437
|
* @remarks
|
|
314
|
-
*
|
|
315
|
-
*
|
|
438
|
+
* Capitalised because `do` is a reserved word. Each step receives the scope
|
|
439
|
+
* accumulated so far; the error types union across `bind`s, and a throw in any
|
|
440
|
+
* step becomes a `Defect`. To go asynchronous, lift the chain with `toAsync()`
|
|
441
|
+
* (then a `bind` may return an `AsyncResult`).
|
|
316
442
|
*
|
|
317
|
-
* @
|
|
318
|
-
*
|
|
319
|
-
|
|
320
|
-
type ErrMatcher<E> = ReturnType<typeof match<E>>;
|
|
321
|
-
/**
|
|
322
|
-
* The shape an error-combinator callback must return: an **exhaustive**
|
|
323
|
-
* match builder. `exhaustive` is required to be *callable* — on a builder
|
|
324
|
-
* that hasn't covered every case the matcher types it as a branded diagnostic
|
|
325
|
-
* (not a function), so a non-exhaustive chain fails to satisfy this and errors
|
|
326
|
-
* at the call site. `run` carries the output type.
|
|
443
|
+
* @example
|
|
444
|
+
* ```ts
|
|
445
|
+
* import { Do, Ok } from "unthrown";
|
|
327
446
|
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
447
|
+
* const result = Do()
|
|
448
|
+
* .bind("user", () => findUser(id)) // Result<User, NotFound>
|
|
449
|
+
* .bind("org", ({ user }) => findOrg(user.orgId)) // Result<Org, NotFound>
|
|
450
|
+
* .let("label", ({ user, org }) => `${user.name} @ ${org.name}`)
|
|
451
|
+
* .map(({ user, org, label }) => render(user, org, label));
|
|
452
|
+
* // Result<View, NotFound>
|
|
453
|
+
* ```
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```ts
|
|
457
|
+
* import { Do, Ok, Err } from "unthrown";
|
|
458
|
+
*
|
|
459
|
+
* // Ok path — the scope accumulates:
|
|
460
|
+
* Do()
|
|
461
|
+
* .bind("a", () => Ok(2))
|
|
462
|
+
* .let("b", ({ a }) => a * 10)
|
|
463
|
+
* .map(({ a, b }) => a + b); // => Ok(22)
|
|
464
|
+
*
|
|
465
|
+
* // Err path — the first Err short-circuits the rest:
|
|
466
|
+
* Do()
|
|
467
|
+
* .bind("a", () => Err("boom"))
|
|
468
|
+
* .let("b", ({ a }) => a); // => Err("boom")
|
|
469
|
+
* ```
|
|
470
|
+
*
|
|
471
|
+
* @category Do-notation
|
|
330
472
|
*/
|
|
331
|
-
|
|
332
|
-
exhaustive: (...args: never[]) => unknown;
|
|
333
|
-
run: () => O;
|
|
334
|
-
};
|
|
473
|
+
export declare function Do(): Result<{}, never>;
|
|
335
474
|
/**
|
|
336
|
-
*
|
|
475
|
+
* Start an **asynchronous** do-notation chain with an empty object scope — the
|
|
476
|
+
* pre-lifted form of {@link Do}, sparing you `Do().toAsync()`.
|
|
337
477
|
*
|
|
338
|
-
* @
|
|
478
|
+
* @remarks
|
|
479
|
+
* From here a `bind` may return a `Result` **or** an `AsyncResult`; the scope
|
|
480
|
+
* accumulates exactly as in a sync {@link Do} chain, and a throw in any step
|
|
481
|
+
* becomes a `Defect`. Named with the `Async` suffix the async free functions
|
|
482
|
+
* carry (`OkAsync`, `allAsync`); the {@link AsyncResult} companion aliases it as
|
|
483
|
+
* `AsyncResult.Do` (the namespace already says "async", so the suffix drops).
|
|
484
|
+
*
|
|
485
|
+
* @example
|
|
486
|
+
* ```ts
|
|
487
|
+
* import { DoAsync, Ok } from "unthrown";
|
|
488
|
+
*
|
|
489
|
+
* const result = await DoAsync()
|
|
490
|
+
* .bind("user", () => findUser(id)) // AsyncResult<User, NotFound>
|
|
491
|
+
* .bind("plan", ({ user }) => Ok(user.plan)) // a sync Result is accepted too
|
|
492
|
+
* .let("label", ({ user, plan }) => `${user.name} on ${plan}`);
|
|
493
|
+
* // Result<{ user: User; plan: Plan; label: string }, NotFound>
|
|
494
|
+
* ```
|
|
495
|
+
*
|
|
496
|
+
* @category Do-notation
|
|
339
497
|
*/
|
|
340
|
-
|
|
498
|
+
export declare function DoAsync(): AsyncResult<{}, never>;
|
|
499
|
+
//#endregion
|
|
500
|
+
//#region src/interop.d.ts
|
|
341
501
|
/**
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* inference as the boundary `qualify`, Thesis #3). A branch that returns
|
|
345
|
-
* `defect(cause)` therefore contributes nothing to the modeled channel.
|
|
502
|
+
* Bridge a nullable value into a {@link Result}: absence becomes a **modeled**
|
|
503
|
+
* `Err`. The sanctioned alternative to an `Option` type.
|
|
346
504
|
*
|
|
347
|
-
* @
|
|
505
|
+
* @remarks
|
|
506
|
+
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
507
|
+
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
508
|
+
*
|
|
509
|
+
* @typeParam T - the (nullable) value type.
|
|
510
|
+
* @typeParam E - the error produced when the value is absent.
|
|
511
|
+
* @param value - the possibly-absent value.
|
|
512
|
+
* @param onAbsent - lazily produces the error for the absent case.
|
|
513
|
+
*
|
|
514
|
+
* @category Interop
|
|
515
|
+
*
|
|
516
|
+
* @example
|
|
517
|
+
* ```ts
|
|
518
|
+
* import { fromNullable } from "unthrown";
|
|
519
|
+
*
|
|
520
|
+
* const map = new Map([["a", 1]]);
|
|
521
|
+
* fromNullable(map.get("a"), () => "absent").getOr(0); // => 1
|
|
522
|
+
* fromNullable(map.get("z"), () => "absent"); // => Err("absent")
|
|
523
|
+
* fromNullable(0, () => "absent").getOr(-1); // => 0 (falsy but present)
|
|
524
|
+
* ```
|
|
348
525
|
*/
|
|
349
|
-
|
|
526
|
+
export declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () => E): Result<NonNullable<T>, E>;
|
|
350
527
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* per entry below. Factored out so the three variants ({@link OkView},
|
|
354
|
-
* {@link ErrView}, {@link DefectView}) can each intersect it; {@link AsyncResult}
|
|
355
|
-
* mirrors this surface with async signatures.
|
|
528
|
+
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
|
529
|
+
* throwing.
|
|
356
530
|
*
|
|
357
531
|
* @remarks
|
|
358
|
-
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
532
|
+
* `qualify` **must** triage every thrown cause into a modeled error `E` or a
|
|
533
|
+
* `Defect` (via the injected `defect` helper, its second argument) — there is no
|
|
534
|
+
* path that leaves `unknown` in `E`. A throw inside `qualify` itself is treated
|
|
535
|
+
* as a `Defect`. `qualify` is **synchronous**: an `async` qualify is rejected at
|
|
536
|
+
* compile time ({@link NotThenable}) — its `Promise` would land in `E` un-triaged
|
|
537
|
+
* — and a thenable slipped past the types at runtime becomes a `Defect` (never
|
|
538
|
+
* an `Err(Promise)`), its orphaned rejection silenced.
|
|
361
539
|
*
|
|
362
|
-
*
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
* the chain short-circuits. Errors union (`E | E2`). A throw becomes a
|
|
435
|
-
* `Defect` — as does calling `bind` on a non-object scope (e.g. `Ok(5).bind`),
|
|
436
|
-
* which is misuse: the scope is always an object inside a real `Do()` chain.
|
|
437
|
-
* (`let` is the pure-value counterpart.)
|
|
438
|
-
*
|
|
439
|
-
* @typeParam K - the key the bound value is stored under.
|
|
440
|
-
* @typeParam U - the bound value type.
|
|
441
|
-
* @typeParam E2 - the error type `f` may introduce.
|
|
442
|
-
* @param name - the scope key.
|
|
443
|
-
* @param f - produces a `Result` from the accumulated scope.
|
|
444
|
-
*/
|
|
445
|
-
bind<K extends string, U, E2>(name: K, f: (scope: T) => Result$1<U, E2>): Result$1<Bound<T, K, U>, E | E2>;
|
|
446
|
-
/**
|
|
447
|
-
* Do-notation: run `f` for a **plain value** and bind it under `name` in the
|
|
448
|
-
* accumulating object scope. The pure-value counterpart of {@link ResultMethods.bind | bind}.
|
|
449
|
-
*
|
|
450
|
-
* @remarks
|
|
451
|
-
* `f` receives the scope and returns a value (not a `Result`); it is added as
|
|
452
|
-
* `{ ...scope, [name]: value }`. Runs only on `Ok`; `Err`/`Defect` pass
|
|
453
|
-
* through. A throw becomes a `Defect`. An async callback is rejected at
|
|
454
|
-
* compile time ({@link NotThenable}).
|
|
455
|
-
*
|
|
456
|
-
* @typeParam K - the key the value is stored under.
|
|
457
|
-
* @typeParam U - the value type.
|
|
458
|
-
* @param name - the scope key.
|
|
459
|
-
* @param f - computes a value from the accumulated scope.
|
|
460
|
-
*/
|
|
461
|
-
let<K extends string, U>(name: K, f: (scope: T) => U & NotThenable<U>): Result$1<Bound<T, K, U>, E>;
|
|
462
|
-
/**
|
|
463
|
-
* Replace the success value with a constant `value`.
|
|
464
|
-
*
|
|
465
|
-
* Runs only on `Ok`; `Err` and `Defect` pass through.
|
|
466
|
-
*
|
|
467
|
-
* @typeParam U - the replacement value type.
|
|
468
|
-
*/
|
|
469
|
-
as<U>(value: U): Result$1<U, E>;
|
|
470
|
-
/**
|
|
471
|
-
* Drop the success value, collapsing the success type to `void`.
|
|
472
|
-
*
|
|
473
|
-
* The named form of `map(() => undefined)`. Runs only on `Ok` (the value is
|
|
474
|
-
* replaced with `undefined`); `Err` and `Defect` pass through. Unlike
|
|
475
|
-
* `as(undefined)` — which produces `Result<undefined, E>` — the success type
|
|
476
|
-
* is `void`: the value's story ends here.
|
|
477
|
-
*/
|
|
478
|
-
discard(): Result$1<void, E>;
|
|
479
|
-
/**
|
|
480
|
-
* Validate the success value — keep the `Ok` when `predicate` holds,
|
|
481
|
-
* otherwise fail into the **modeled** channel with `Err(onFail(value))`.
|
|
482
|
-
*
|
|
483
|
-
* @remarks
|
|
484
|
-
* The named form of `flatMap((v) => (p(v) ? Ok(v) : Err(e)))`. With a
|
|
485
|
-
* **type-guard** predicate (`(v): v is U`) the success type is **refined** to
|
|
486
|
-
* `U` on the way through (this overload). Runs only on `Ok` — a passing value
|
|
487
|
-
* flows through as the *same* `Ok`; `Err` and `Defect` pass through
|
|
488
|
-
* untouched. A throw in `predicate` or `onFail` becomes a `Defect`.
|
|
489
|
-
*
|
|
490
|
-
* Both callbacks are synchronous: an async `onFail` is rejected at compile
|
|
491
|
-
* time ({@link NotThenable}), and an async predicate does not type-check
|
|
492
|
-
* either — its `Promise<boolean>` is not a `boolean` (and, being truthy,
|
|
493
|
-
* would have silently always passed).
|
|
494
|
-
*
|
|
495
|
-
* @typeParam U - the refined success type (type-guard form).
|
|
496
|
-
* @typeParam E2 - the error type `onFail` produces.
|
|
497
|
-
* @param predicate - the check; a type guard refines `T` to `U`.
|
|
498
|
-
* @param onFail - maps the failing value to the modeled error.
|
|
499
|
-
*
|
|
500
|
-
* @example
|
|
501
|
-
* ```ts
|
|
502
|
-
* // boolean form: gate a value
|
|
503
|
-
* Ok(-1).ensure((n) => n > 0, (n) => `negative: ${n}`); // Err("negative: -1")
|
|
504
|
-
*
|
|
505
|
-
* // type-guard form: refine the success type
|
|
506
|
-
* declare const r: Result<string | number, "e">;
|
|
507
|
-
* const s = r.ensure(
|
|
508
|
-
* (v): v is string => typeof v === "string",
|
|
509
|
-
* () => "not_a_string" as const,
|
|
510
|
-
* ); // Result<string, "e" | "not_a_string">
|
|
511
|
-
* ```
|
|
512
|
-
*/
|
|
513
|
-
ensure<U extends T, E2>(predicate: (value: T) => value is U, onFail: (value: T) => E2 & NotThenable<E2>): Result$1<U, E | E2>;
|
|
514
|
-
/**
|
|
515
|
-
* Boolean form of {@link ResultMethods.ensure | ensure} — validates without
|
|
516
|
-
* refining, keeping the success type `T`.
|
|
517
|
-
*/
|
|
518
|
-
ensure<E2>(predicate: (value: T) => boolean, onFail: (value: T) => E2 & NotThenable<E2>): Result$1<T, E | E2>;
|
|
519
|
-
/**
|
|
520
|
-
* Transform the modeled error by **matching it exhaustively**.
|
|
521
|
-
*
|
|
522
|
-
* @remarks
|
|
523
|
-
* The callback receives `match(error)` (an {@link ErrMatcher}) and the
|
|
524
|
-
* injected `defect` helper. Chain `.with(pattern, handler)` and **return the
|
|
525
|
-
* un-terminated builder** — `mapErrCases` calls `.exhaustive()` itself, so a
|
|
526
|
-
* missing case is a compile error at the call site (there is no `.exhaustive()`
|
|
527
|
-
* to forget, and no way to slip in `.otherwise()`). The outgoing error type is
|
|
528
|
-
* the union of the branch returns with the `Defect` arm subtracted
|
|
529
|
-
* (`Exclude<O, Defect>`) — a branch returning `defect(cause)` converts that case
|
|
530
|
-
* to a `Defect` and drops it from `E`. Runs only on `Err`; `Ok` and `Defect`
|
|
531
|
-
* pass through. A branch that throws also becomes a `Defect`.
|
|
532
|
-
*
|
|
533
|
-
* **Name every case.** Match on anything the matcher supports — `_tag`,
|
|
534
|
-
* `code`, structural shape, guards — and group the cases that share a handler
|
|
535
|
-
* with `.with(a, b, handler)`. `.with(P._, …)` is the wildcard **escape
|
|
536
|
-
* hatch**, not the default: it makes any match exhaustive, so it also absorbs
|
|
537
|
-
* every case `E` grows later. Two uses are sanctioned — a helper generic in
|
|
538
|
-
* `E`, where no arm list can prove exhaustiveness against an unresolved type
|
|
539
|
-
* parameter, and an `E` that is a single type rather than a union of cases
|
|
540
|
-
* (see {@link P} for both). `@unthrown/oxlint`'s `no-catch-all-pattern` (in
|
|
541
|
-
* its `recommended` preset) flags the rest.
|
|
542
|
-
*
|
|
543
|
-
* @typeParam M - the exhaustive builder the callback returns.
|
|
544
|
-
* @param f - builds the match over the error (returns the un-terminated builder).
|
|
545
|
-
*/
|
|
546
|
-
mapErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): Result$1<T, MatchErrOut<M>>;
|
|
547
|
-
/**
|
|
548
|
-
* Sequence from an `Err` by producing another `Result` — the error-channel
|
|
549
|
-
* mirror of {@link ResultMethods.flatMap | flatMap}, **matching the error
|
|
550
|
-
* exhaustively** ({@link ErrMatcher}; the combinator calls `.exhaustive()`).
|
|
551
|
-
*
|
|
552
|
-
* Each branch returns a `Result`; the outgoing channels are the unions of the
|
|
553
|
-
* branch-returned `Result`s' channels. A branch may return `defect(cause)`.
|
|
554
|
-
* Runs only on `Err`; `Ok` and `Defect` pass through.
|
|
555
|
-
*
|
|
556
|
-
* @typeParam M - the exhaustive builder the callback returns.
|
|
557
|
-
* @param f - builds the match; each branch produces a fallback `Result`.
|
|
558
|
-
*/
|
|
559
|
-
flatMapErrCases<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>>>;
|
|
560
|
-
/**
|
|
561
|
-
* Recover from an `Err` by producing a success value, emptying the error
|
|
562
|
-
* channel — **matching the error exhaustively** ({@link ErrMatcher}). Pairs
|
|
563
|
-
* with {@link ResultMethods.recoverDefect | recoverDefect}.
|
|
564
|
-
*
|
|
565
|
-
* @remarks
|
|
566
|
-
* The result type is `Result<T | U, never>`, but `never` describes only the
|
|
567
|
-
* **error** channel — a `Defect` can still be present at runtime. A branch may
|
|
568
|
-
* return `defect(cause)` (which stays a `Defect`, not a recovery). Runs only on
|
|
569
|
-
* `Err`; `Ok` and `Defect` pass through.
|
|
570
|
-
*
|
|
571
|
-
* @typeParam M - the exhaustive builder the callback returns.
|
|
572
|
-
* @param f - builds the match; each branch produces a success value.
|
|
573
|
-
*/
|
|
574
|
-
recoverErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): Result$1<T | MatchErrOut<M>, never>;
|
|
575
|
-
/**
|
|
576
|
-
* Run a side effect on the error — **matched exhaustively** ({@link ErrMatcher})
|
|
577
|
-
* — and pass the `Result` through unchanged.
|
|
578
|
-
*
|
|
579
|
-
* @remarks
|
|
580
|
-
* The callback builds a match whose branches run side effects; their return
|
|
581
|
-
* values are ignored and the original `Err` flows through. Exhaustive like the
|
|
582
|
-
* transformers, and like them it wants every case named — `.with(P._, …)`
|
|
583
|
-
* remains the wildcard escape hatch. If a branch throws, the
|
|
584
|
-
* result is a `Defect` whose cause is an `AggregateError` of `[thrown, original
|
|
585
|
-
* failure]` — observing a failure never destroys it. An **async branch is
|
|
586
|
-
* rejected at compile time** ({@link NotThenable} on the builder output):
|
|
587
|
-
* because the branch results are discarded, a returned `Promise` would float
|
|
588
|
-
* unobserved and its rejection would vanish. The one branch return that is
|
|
589
|
-
* **not** discarded is the injected `defect(cause)` marker: it is the
|
|
590
|
-
* lint-clean, expression-position form of a `throw`, so it follows the throw
|
|
591
|
-
* rule above (an `AggregateError` of `[the branch's cause, original
|
|
592
|
-
* failure]`), never a silent no-op. A failable
|
|
593
|
-
* `Result`-returning effect belongs in
|
|
594
|
-
* {@link ResultMethods.flatTapErrCases | flatTapErrCases}.
|
|
595
|
-
*
|
|
596
|
-
* @param f - builds the match; branch returns are ignored, bar `defect(cause)`.
|
|
597
|
-
*/
|
|
598
|
-
tapErrCases<R>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<R & NotThenable<R>>): Result$1<T, E>;
|
|
599
|
-
/**
|
|
600
|
-
* Run a **failable** side effect on the error, keeping the original error but
|
|
601
|
-
* threading the effect's own error — **matched exhaustively**
|
|
602
|
-
* ({@link ErrMatcher}).
|
|
603
|
-
*
|
|
604
|
-
* @remarks
|
|
605
|
-
* The error-channel mirror of {@link ResultMethods.flatTap | flatTap}: each
|
|
606
|
-
* branch returns a `Result` whose **success value is discarded** — on the
|
|
607
|
-
* effect's `Ok` the original `Err` flows through, while an `Err`/`Defect` from a
|
|
608
|
-
* branch short-circuits and threads its error. Note the asymmetry with a
|
|
609
|
-
* *throw*: a branch that **returns** a Defect-state `Result` **replaces** the
|
|
610
|
-
* original `Err` (Defect-dominance, the short-circuit rule — it is not
|
|
611
|
-
* aggregated), whereas a branch that **throws** produces a `Defect`
|
|
612
|
-
* aggregating `[thrown, original failure]` (observing a failure by throwing
|
|
613
|
-
* never destroys it). A branch returning the injected `defect(cause)` marker —
|
|
614
|
-
* reachable under a `returnType` pin — follows the *throw* rule, since it is
|
|
615
|
-
* the lint-clean, expression-position form of one.
|
|
616
|
-
*
|
|
617
|
-
* @typeParam M - the exhaustive builder the callback returns.
|
|
618
|
-
* @param f - builds the match; each branch is a failable effect (its `Ok` is ignored).
|
|
619
|
-
*/
|
|
620
|
-
flatTapErrCases<E2>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<Result$1<unknown, E2>>): Result$1<T, E | E2>;
|
|
621
|
-
/**
|
|
622
|
-
* Recover from a `Defect` — the **only** combinator that can touch one.
|
|
623
|
-
*
|
|
624
|
-
* @remarks
|
|
625
|
-
* Runs `f` only when a `Defect` is present, re-entering the modeled world by
|
|
626
|
-
* returning a `Result` (an `Ok` or a fresh `Err`). `Ok` and `Err` pass
|
|
627
|
-
* through. Recovering a Defect should be rare: usually you let it bubble to
|
|
628
|
-
* the edge. If `f` throws, the throw becomes a new `Defect`.
|
|
629
|
-
*
|
|
630
|
-
* @typeParam U - a success type the recovery may produce.
|
|
631
|
-
* @typeParam E2 - an error type the recovery may produce.
|
|
632
|
-
* @param f - maps the Defect's unknown cause to a recovering `Result`.
|
|
633
|
-
*/
|
|
634
|
-
recoverDefect<U, E2>(f: (cause: unknown) => Result$1<U, E2>): Result$1<T | U, E | E2>;
|
|
635
|
-
/**
|
|
636
|
-
* Run a side effect on a present `Defect`'s cause (e.g. logging) and pass the
|
|
637
|
-
* `Defect` through unchanged. If `f` throws, the result is a `Defect` whose
|
|
638
|
-
* cause is an `AggregateError` of `[thrown, original failure]` — observing a
|
|
639
|
-
* failure never destroys it. An async callback is rejected at compile time
|
|
640
|
-
* ({@link NotThenable}).
|
|
641
|
-
*
|
|
642
|
-
* @param f - the side effect over the unknown cause.
|
|
643
|
-
*/
|
|
644
|
-
tapDefect<R>(f: (cause: unknown) => R & NotThenable<R>): Result$1<T, E>;
|
|
645
|
-
/**
|
|
646
|
-
* Run a side effect on **any failure** — `Err` or `Defect` — and pass the
|
|
647
|
-
* `Result` through unchanged. The one cross-channel observer, for the shared
|
|
648
|
-
* "it went KO" concern (logging, metrics, rollback) that would otherwise be
|
|
649
|
-
* duplicated across {@link ResultMethods.tapErrCases | tapErrCases} and
|
|
650
|
-
* {@link ResultMethods.tapDefect | tapDefect}.
|
|
651
|
-
*
|
|
652
|
-
* @remarks
|
|
653
|
-
* `f` receives the narrowed **failure variant** ({@link FailureView}), not a
|
|
654
|
-
* payload — the payload union `E | unknown` would collapse to `unknown` and
|
|
655
|
-
* lose `E`'s typing. Branch on `failure.tag` to reach the typed payload
|
|
656
|
-
* (`"Err"` → `failure.error: E`, `"Defect"` → `failure.cause: unknown`), or
|
|
657
|
-
* treat it opaquely for a shared logger. Runs on `Err` and `Defect`; `Ok`
|
|
658
|
-
* passes through. It **observes without consuming**: the failure flows on
|
|
659
|
-
* unchanged — to also recover, use
|
|
660
|
-
* {@link ResultMethods.recoverErrCases | recoverErrCases} /
|
|
661
|
-
* {@link ResultMethods.recoverDefect | recoverDefect} (deliberately separate
|
|
662
|
-
* acts) or {@link ResultMethods.match | match} at the edge. If `f` throws, the
|
|
663
|
-
* result is a `Defect` whose cause is an `AggregateError` of `[thrown,
|
|
664
|
-
* original failure]` — observing a failure never destroys it. An async
|
|
665
|
-
* callback is rejected at compile time ({@link NotThenable}).
|
|
666
|
-
*
|
|
667
|
-
* @param f - the side effect over the failure variant (its return value is ignored).
|
|
668
|
-
*/
|
|
669
|
-
tapFailure<R>(f: (failure: FailureView<E, T>) => R & NotThenable<R>): Result$1<T, E>;
|
|
670
|
-
/**
|
|
671
|
-
* Exhaustively fold all three runtime states into a single value.
|
|
672
|
-
*
|
|
673
|
-
* @remarks
|
|
674
|
-
* Exactly one handler runs. Together with the throw-to-Defect guarantee, this
|
|
675
|
-
* is typically the single place a pipeline is handled at the edge — mapping
|
|
676
|
-
* `Ok`/`Err`/`Defect` to (for example) 2xx / 4xx / 5xx with no `try`/`catch`.
|
|
677
|
-
*
|
|
678
|
-
* The `errCases` handler does not take a single blanket callback: it receives
|
|
679
|
-
* `match(error)` (an {@link ErrMatcher}) and **matches the error exhaustively**,
|
|
680
|
-
* exactly like the error combinators — which is why the key carries the same
|
|
681
|
-
* `…Cases` suffix. Chain `.with(pattern, handler)` and **return the
|
|
682
|
-
* un-terminated builder** — `match` calls `.exhaustive()` itself, so a missing
|
|
683
|
-
* case is a compile error at the call site (no `.exhaustive()` to forget).
|
|
684
|
-
* Folding at the edge names every case too — `.with(P._, …)` is the wildcard
|
|
685
|
-
* escape hatch, not the default. Unlike the combinators the branches
|
|
686
|
-
* receive **no `defect` helper** — `match` is total elimination to a value,
|
|
687
|
-
* with no `Defect` output channel; the `defect` case handles a `Result` that
|
|
688
|
-
* already carries one. (A `Result` is also a discriminated union — for richer
|
|
689
|
-
* whole-`Result` matching, `match(result).with(…)`.)
|
|
690
|
-
*
|
|
691
|
-
* @typeParam ROk - the `ok` handler return type.
|
|
692
|
-
* @typeParam RDefect - the `defect` handler return type.
|
|
693
|
-
* @typeParam M - the exhaustive builder the `errCases` handler returns.
|
|
694
|
-
* @param cases - the `ok`/`defect` handlers plus the `errCases` matcher builder.
|
|
695
|
-
*/
|
|
696
|
-
match<ROk, RDefect, M extends ExhaustiveMatch<unknown>>(cases: {
|
|
697
|
-
ok: (value: T) => ROk;
|
|
698
|
-
errCases: (matcher: ErrMatcher<E>) => M;
|
|
699
|
-
defect: (cause: unknown) => RDefect;
|
|
700
|
-
}): ROk | RDefect | MatchOut<M>;
|
|
701
|
-
/**
|
|
702
|
-
* Extract the success value.
|
|
703
|
-
*
|
|
704
|
-
* @remarks
|
|
705
|
-
* Compiles only when the error channel is empty (`E = never`) — eliminate
|
|
706
|
-
* modeled errors first (`match` / `recoverErrCases` / `flatMapErrCases`), or reach for the
|
|
707
|
-
* `getOr` / `getOrElse` / `getOrNull` / `getOrUndefined` family (which
|
|
708
|
-
* recover an `Err`). If you get a `'this' context` type error here, that is
|
|
709
|
-
* the gate: the receiver still has a non-`never` error channel.
|
|
710
|
-
*
|
|
711
|
-
* `E = never` empties only the **modeled** error channel — a `Defect` can
|
|
712
|
-
* still be present, and `get()` **rethrows its original cause** (it
|
|
713
|
-
* _panics_); `Result<T, never>` does not mean `get()` cannot throw.
|
|
714
|
-
*
|
|
715
|
-
* @returns the `Ok` value.
|
|
716
|
-
*/
|
|
717
|
-
get(this: Result$1<T, never>): T;
|
|
718
|
-
/**
|
|
719
|
-
* Extract the modeled error.
|
|
720
|
-
*
|
|
721
|
-
* @remarks
|
|
722
|
-
* Compiles only when the success channel is empty (`T = never`) — eliminate
|
|
723
|
-
* the success case first. `T = never` is rarely the case in practice (a
|
|
724
|
-
* `Result` you hold usually still has a success type), so to inspect an
|
|
725
|
-
* error prefer an `isErr()` guard or, in tests, `@unthrown/vitest`'s
|
|
726
|
-
* `toBeErrWith`. A `Defect` still **rethrows its original cause** (a defect is
|
|
727
|
-
* a bug, not an absent value), so this does not mean `getErr()` can't throw.
|
|
728
|
-
*
|
|
729
|
-
* @returns the `Err` value.
|
|
730
|
-
*/
|
|
731
|
-
getErr(this: Result$1<never, E>): E;
|
|
732
|
-
/**
|
|
733
|
-
* The success value, or `fallback` on `Err`.
|
|
734
|
-
*
|
|
735
|
-
* @typeParam U - the fallback type (may differ from `T`; the return widens to `T | U`).
|
|
736
|
-
* @param fallback - returned when the result is an `Err` (may be a different type; the return widens to `T | U`).
|
|
737
|
-
* @throws Re-throws on a `Defect` — a Defect is a bug, not an absent value, so
|
|
738
|
-
* it is never silently replaced.
|
|
739
|
-
*/
|
|
740
|
-
getOr<U>(fallback: U): T | U;
|
|
741
|
-
/**
|
|
742
|
-
* The success value, or `f(error)` on `Err`.
|
|
743
|
-
*
|
|
744
|
-
* @typeParam U - the fallback type (may differ from `T`; the return widens to `T | U`).
|
|
745
|
-
* @param f - lazily computes the fallback from the error (may return a different type; the return widens to `T | U`).
|
|
746
|
-
* @throws Re-throws on a `Defect`.
|
|
747
|
-
*/
|
|
748
|
-
getOrElse<U>(f: (error: E) => U): T | U;
|
|
749
|
-
/**
|
|
750
|
-
* The success value, or `null` on `Err`.
|
|
751
|
-
*
|
|
752
|
-
* @throws Re-throws on a `Defect`.
|
|
753
|
-
*/
|
|
754
|
-
getOrNull(): T | null;
|
|
755
|
-
/**
|
|
756
|
-
* The success value, or `undefined` on `Err`.
|
|
757
|
-
*
|
|
758
|
-
* @throws Re-throws on a `Defect`.
|
|
759
|
-
*/
|
|
760
|
-
getOrUndefined(): T | undefined;
|
|
761
|
-
/**
|
|
762
|
-
* The success value, or **throw** the modeled error on `Err`.
|
|
763
|
-
*
|
|
764
|
-
* @remarks
|
|
765
|
-
* A deliberate escape hatch off the errors-as-values model — it **throws the
|
|
766
|
-
* `Err` value as-is** at the call site, so a caller of the enclosing function
|
|
767
|
-
* sees a throw rather than a channel. Its home is **tests and scripts**,
|
|
768
|
-
* where "this `Result` had better be `Ok`" is the assertion and a throw is
|
|
769
|
-
* the correct failure mode.
|
|
770
|
-
*
|
|
771
|
-
* In production code, fold the error channel instead:
|
|
772
|
-
* {@link ResultMethods.recoverErrCases | recoverErrCases} empties `E`, so
|
|
773
|
-
* {@link ResultMethods.get | get} compiles and a case routed to the injected
|
|
774
|
-
* `defect(...)` panics with its original cause — with every case still named.
|
|
775
|
-
* {@link ResultMethods.match | match} and
|
|
776
|
-
* {@link ResultMethods.flatMapErrCases | flatMapErrCases} are the other two
|
|
777
|
-
* ways to keep the error a value. `@unthrown/oxlint`'s opt-in
|
|
778
|
-
* `no-get-or-throw` rule enforces this, exempting test files through an
|
|
779
|
-
* oxlint `overrides` entry.
|
|
780
|
-
*
|
|
781
|
-
* Type-gated as the **complement** of {@link ResultMethods.get | get}: it
|
|
782
|
-
* compiles only when the error channel is **non-empty** (`E` is not `never`) —
|
|
783
|
-
* there must be a modeled error for it to throw. On a `Result<T, never>` there
|
|
784
|
-
* is nothing to throw, so `getOrThrow` does not compile; use `get()` (which
|
|
785
|
-
* gates the other way). Together they partition extraction by the error
|
|
786
|
-
* channel's state, with no overlap.
|
|
787
|
-
*
|
|
788
|
-
* @returns the `Ok` value.
|
|
789
|
-
* @throws the modeled `error` on `Err`; re-throws the original `cause` on a
|
|
790
|
-
* `Defect` (a panic, like the rest of the `getOr…` family).
|
|
791
|
-
*/
|
|
792
|
-
getOrThrow(this: [E] extends [never] ? "unthrown: getOrThrow is unnecessary here — the Err channel is empty (E = never), so there is nothing to throw. Use get() instead." : Result$1<T, E>): T;
|
|
793
|
-
/** Whether this result is `Ok` — narrows `this` to its {@link OkView} on `true`. */
|
|
794
|
-
isOk(): this is OkView<T, E>;
|
|
795
|
-
/** Whether this result is `Err` — narrows `this` to its {@link ErrView} on `true`. */
|
|
796
|
-
isErr(): this is ErrView<E, T>;
|
|
797
|
-
/** Whether this result is a `Defect` — narrows `this` to its {@link DefectView} on `true`. */
|
|
798
|
-
isDefect(): this is DefectView<T, E>;
|
|
799
|
-
/** Lift this synchronous `Result` into an {@link AsyncResult}. */
|
|
800
|
-
toAsync(): AsyncResult$1<T, E>;
|
|
801
|
-
};
|
|
540
|
+
* `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
|
|
541
|
+
* already returned, so its rejection could never reach `qualify`: it becomes a
|
|
542
|
+
* `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
|
|
543
|
+
* than left to float. Reach for {@link fromPromise} to wrap async work.
|
|
544
|
+
*
|
|
545
|
+
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
546
|
+
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
547
|
+
* `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
|
|
548
|
+
* out-of-band and must not pollute the error channel); reach for
|
|
549
|
+
* {@link fromSafeThrowable} when every throw is a Defect.
|
|
550
|
+
*
|
|
551
|
+
* @typeParam A - the wrapped function's argument tuple.
|
|
552
|
+
* @typeParam T - the wrapped function's return type.
|
|
553
|
+
* @typeParam R - `qualify`'s return type; the modeled error `E` is
|
|
554
|
+
* `Exclude<R, Defect>` (its `Defect` arm, if any, is subtracted).
|
|
555
|
+
* @param fn - the throwing function to wrap.
|
|
556
|
+
* @param qualify - triages a thrown `cause` into a modeled `E`, or marks it
|
|
557
|
+
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
558
|
+
* @returns a function with the same arguments returning `Result<T, E>`.
|
|
559
|
+
*
|
|
560
|
+
* @category Interop
|
|
561
|
+
*
|
|
562
|
+
* @example
|
|
563
|
+
* ```ts
|
|
564
|
+
* import { fromThrowable } from "unthrown";
|
|
565
|
+
*
|
|
566
|
+
* // Model the parse failure as an `Err`, everything unexpected as a `Defect`.
|
|
567
|
+
* const parse = fromThrowable(
|
|
568
|
+
* (text: string) => JSON.parse(text) as unknown,
|
|
569
|
+
* (cause, defect) =>
|
|
570
|
+
* cause instanceof SyntaxError ? ("invalid_json" as const) : defect(cause),
|
|
571
|
+
* );
|
|
572
|
+
*
|
|
573
|
+
* parse('{"ok":true}').getOr(null); // => { ok: true }
|
|
574
|
+
* parse("nope"); // => Err("invalid_json")
|
|
575
|
+
* ```
|
|
576
|
+
*/
|
|
577
|
+
export declare function fromThrowable<A extends unknown[], T, R>(fn: (...args: A) => T, qualify: (cause: unknown, defect: (cause: unknown) => Defect) => R & NotThenable<R>): (...args: A) => Result<T, Exclude<R, Defect>>;
|
|
578
|
+
/**
|
|
579
|
+
* Wrap a throwing synchronous function asserted **not** to fail in any modeled
|
|
580
|
+
* way: any throw becomes a `Defect`.
|
|
581
|
+
*
|
|
582
|
+
* @remarks
|
|
583
|
+
* The synchronous counterpart of {@link fromSafePromise}. Use it only when a
|
|
584
|
+
* throw genuinely indicates a bug rather than an anticipated outcome — the
|
|
585
|
+
* error channel is `never`, so there is nothing to triage; there is no
|
|
586
|
+
* `qualify`. When some throws *are* anticipated, reach for
|
|
587
|
+
* {@link fromThrowable} and triage them.
|
|
588
|
+
*
|
|
589
|
+
* `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
|
|
590
|
+
* `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
|
|
591
|
+
* float. Reach for {@link fromSafePromise} to wrap async work.
|
|
592
|
+
*
|
|
593
|
+
* @typeParam A - the wrapped function's argument tuple.
|
|
594
|
+
* @typeParam T - the wrapped function's return type.
|
|
595
|
+
* @param fn - the throwing function to wrap.
|
|
596
|
+
* @returns a function with the same arguments returning `Result<T, never>`.
|
|
597
|
+
*
|
|
598
|
+
* @category Interop
|
|
599
|
+
*
|
|
600
|
+
* @example
|
|
601
|
+
* ```ts
|
|
602
|
+
* import { fromSafeThrowable } from "unthrown";
|
|
603
|
+
*
|
|
604
|
+
* // A decode failure here is a bug (the row came from our own schema), so
|
|
605
|
+
* // every throw is a defect — no throwaway `(cause, defect) => defect(cause)`.
|
|
606
|
+
* const decode = fromSafeThrowable((row: Row) => userSchema.parse(row));
|
|
607
|
+
*
|
|
608
|
+
* decode(row); // => Result<User, never> — a throw becomes a Defect
|
|
609
|
+
* ```
|
|
610
|
+
*/
|
|
611
|
+
export declare function fromSafeThrowable<A extends unknown[], T>(fn: (...args: A) => T): (...args: A) => Result<T, never>;
|
|
802
612
|
/**
|
|
803
|
-
*
|
|
804
|
-
*
|
|
805
|
-
*
|
|
613
|
+
* Wrap a `Promise` (or a thunk producing one) as an {@link AsyncResult}, forcing
|
|
614
|
+
* every rejection to be triaged.
|
|
615
|
+
*
|
|
616
|
+
* @remarks
|
|
617
|
+
* `qualify` **must** map each rejection cause into a modeled error `E` or a
|
|
618
|
+
* `Defect` (via the injected `defect` helper, its second argument). The returned
|
|
619
|
+
* `AsyncResult`'s internal promise never rejects; `await`-ing it always yields a
|
|
620
|
+
* `Result`. A throw inside `qualify` is itself a `Defect`. `qualify` is
|
|
621
|
+
* **synchronous**: an `async` qualify is rejected at compile time
|
|
622
|
+
* ({@link NotThenable}), and a thenable slipped past the types at runtime
|
|
623
|
+
* becomes a `Defect` (never an `Err(Promise)`), its orphaned rejection silenced.
|
|
624
|
+
*
|
|
625
|
+
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
626
|
+
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
627
|
+
* `qualify` that returns *only* `defect(cause)` yields `E = never`; when every
|
|
628
|
+
* rejection is a Defect, prefer {@link fromSafePromise}.
|
|
629
|
+
*
|
|
630
|
+
* @typeParam T - the resolved value type.
|
|
631
|
+
* @typeParam R - `qualify`'s return type; the modeled error `E` is
|
|
632
|
+
* `Exclude<R, Defect>` (its `Defect` arm, if any, is subtracted).
|
|
633
|
+
* @param promise - the promise, or a thunk returning one.
|
|
634
|
+
* @param qualify - triages a rejection `cause` into a modeled `E`, or marks it
|
|
635
|
+
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
636
|
+
* @param _guard - compile-time only; never pass it. The phantom rest-tuple that
|
|
637
|
+
* enforces "qualify is synchronous": an `async` qualify makes this demand an
|
|
638
|
+
* impossible extra argument (whose type spells out the error), while a
|
|
639
|
+
* synchronous one leaves it empty. Encoded here — not on `qualify`'s return
|
|
640
|
+
* type — so `T`'s inference from `promise` is undisturbed.
|
|
641
|
+
*
|
|
642
|
+
* @category Interop
|
|
806
643
|
*
|
|
807
644
|
* @example
|
|
808
645
|
* ```ts
|
|
809
|
-
*
|
|
810
|
-
* ```
|
|
646
|
+
* import { fromPromise } from "unthrown";
|
|
811
647
|
*
|
|
812
|
-
*
|
|
648
|
+
* // A rejection with a NotFoundError becomes a modeled `Err`; anything else a Defect.
|
|
649
|
+
* const user = await fromPromise(fetchUser(id), (cause, defect) =>
|
|
650
|
+
* cause instanceof NotFoundError ? ("not_found" as const) : defect(cause),
|
|
651
|
+
* );
|
|
652
|
+
*
|
|
653
|
+
* if (user.isOk()) user.value; // => the fetched user
|
|
654
|
+
* // when fetchUser rejects with NotFoundError: user is Err("not_found")
|
|
655
|
+
* ```
|
|
813
656
|
*/
|
|
814
|
-
|
|
815
|
-
readonly tag: "Ok";
|
|
816
|
-
readonly value: T;
|
|
817
|
-
}
|
|
657
|
+
export declare function fromPromise<T, R>(promise: Promise<T> | (() => Promise<T>), qualify: (cause: unknown, defect: (cause: unknown) => Defect) => R, ..._guard: [Extract<R, PromiseLike<unknown>>] extends [never] ? [] : ["unthrown: qualify must be synchronous — its Promise would land in E un-triaged"]): AsyncResult<T, Exclude<R, Defect>>;
|
|
818
658
|
/**
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
* carries the shared fluent surface ({@link ResultMethods}).
|
|
659
|
+
* Wrap a `Promise` asserted **not** to fail in any modeled way: any rejection
|
|
660
|
+
* becomes a `Defect`.
|
|
822
661
|
*
|
|
823
662
|
* @remarks
|
|
824
|
-
*
|
|
825
|
-
*
|
|
826
|
-
*
|
|
827
|
-
*
|
|
828
|
-
*
|
|
829
|
-
*
|
|
663
|
+
* Use this only when a rejection genuinely indicates a bug rather than an
|
|
664
|
+
* anticipated outcome — the error channel is `never`, so there is nothing to
|
|
665
|
+
* triage. (`await`-ing still yields a `Result`; it never throws.) The
|
|
666
|
+
* synchronous counterpart is {@link fromSafeThrowable}.
|
|
667
|
+
*
|
|
668
|
+
* @typeParam T - the resolved value type.
|
|
669
|
+
* @param promise - the promise, or a thunk returning one.
|
|
670
|
+
*
|
|
671
|
+
* @category Interop
|
|
830
672
|
*
|
|
831
673
|
* @example
|
|
832
674
|
* ```ts
|
|
833
|
-
*
|
|
675
|
+
* import { fromSafePromise } from "unthrown";
|
|
676
|
+
*
|
|
677
|
+
* (await fromSafePromise(Promise.resolve(3))).get(); // => 3
|
|
678
|
+
* // a rejection becomes a Defect (never a modeled Err):
|
|
679
|
+
* await fromSafePromise(Promise.reject(new Error("boom"))); // => Defect(Error("boom"))
|
|
834
680
|
* ```
|
|
681
|
+
*/
|
|
682
|
+
export declare function fromSafePromise<T>(promise: Promise<T> | (() => Promise<T>)): AsyncResult<T, never>;
|
|
683
|
+
/**
|
|
684
|
+
* The settler a {@link fromExecutor} executor receives. Settles the pending
|
|
685
|
+
* `AsyncResult` **once** — later calls are no-ops, exactly as `resolve` is on a
|
|
686
|
+
* `Promise`.
|
|
687
|
+
*
|
|
688
|
+
* @typeParam T - the success type.
|
|
689
|
+
* @typeParam E - the modeled error type.
|
|
835
690
|
*
|
|
836
691
|
* @category Types
|
|
837
692
|
*/
|
|
838
|
-
|
|
839
|
-
readonly tag: "Err";
|
|
840
|
-
readonly error: E;
|
|
841
|
-
}
|
|
693
|
+
type Settle<T, E> = (result: Result<T, E> | Defect) => void;
|
|
842
694
|
/**
|
|
843
|
-
*
|
|
844
|
-
*
|
|
845
|
-
*
|
|
695
|
+
* Build an {@link AsyncResult} from a callback-style API — this library's
|
|
696
|
+
* answer to `new Promise((resolve, reject) => …)`.
|
|
697
|
+
*
|
|
698
|
+
* @remarks
|
|
699
|
+
* The settler takes a **`Result`**, not a value-or-reason pair: the caller names
|
|
700
|
+
* the variant, so no `unknown` can enter `E` and there is no `qualify` to pass.
|
|
701
|
+
* For a failure that is *not* modeled, settle the injected `defect` helper's
|
|
702
|
+
* marker — the same injection `qualify` receives, and the only way to reach the
|
|
703
|
+
* defect channel from inside an asynchronous callback (a `throw` there runs in
|
|
704
|
+
* its own turn, long after the executor body returned).
|
|
705
|
+
*
|
|
706
|
+
* `T` and `E` cannot be inferred from the body, since `settle` is a parameter.
|
|
707
|
+
* Supply them explicitly, or let them flow from an annotated target. Absent
|
|
708
|
+
* either, both default to `never` (Thesis #3: no path may produce `unknown` in
|
|
709
|
+
* `E`) — so an unannotated call is a compile error at the `settle(...)` call
|
|
710
|
+
* site, not a silently-`unknown` channel.
|
|
711
|
+
*
|
|
712
|
+
* An executor that never settles yields an `AsyncResult` that never resolves —
|
|
713
|
+
* the one hazard {@link fromPromise} does not have, and identical to
|
|
714
|
+
* `new Promise`.
|
|
715
|
+
*
|
|
716
|
+
* @typeParam T - the success type.
|
|
717
|
+
* @typeParam E - the modeled error type.
|
|
718
|
+
* @param executor - runs immediately; receives the settler and the `defect` helper.
|
|
719
|
+
*
|
|
720
|
+
* @category Interop
|
|
846
721
|
*
|
|
847
722
|
* @example
|
|
848
723
|
* ```ts
|
|
849
|
-
*
|
|
724
|
+
* import { fromExecutor, Err, Ok } from "unthrown";
|
|
725
|
+
*
|
|
726
|
+
* const listen = (port: number) =>
|
|
727
|
+
* fromExecutor<Server, PortInUse>((settle, defect) => {
|
|
728
|
+
* server.once("error", (cause) =>
|
|
729
|
+
* isAddrInUse(cause) ? settle(Err(new PortInUse(port))) : settle(defect(cause)),
|
|
730
|
+
* );
|
|
731
|
+
* server.listen(port, () => settle(Ok(server)));
|
|
732
|
+
* });
|
|
850
733
|
* ```
|
|
734
|
+
*/
|
|
735
|
+
export declare function fromExecutor<T = never, E = never>(executor: (settle: Settle<T, E>, defect: (cause: unknown) => Defect) => void): AsyncResult<T, E>;
|
|
736
|
+
/**
|
|
737
|
+
* The success channel of {@link all} / {@link allAsync}: a **positional tuple**
|
|
738
|
+
* for a fixed-length input (including the empty tuple), or a homogeneous
|
|
739
|
+
* **array** for a dynamic one.
|
|
851
740
|
*
|
|
852
|
-
* @
|
|
741
|
+
* @remarks
|
|
742
|
+
* The split keys off the input's `length`: a fixed tuple has a literal length
|
|
743
|
+
* (`number extends Rs["length"]` is false → keep the positional `Ts`), while a
|
|
744
|
+
* general array has `length: number` (→ collapse to `Ts[number][]`). Checking
|
|
745
|
+
* length rather than `Rs extends [unknown, ...unknown[]]` keeps `all([])` typed
|
|
746
|
+
* as `Result<[], …>` instead of `Result<never[], …>`.
|
|
747
|
+
*
|
|
748
|
+
* @typeParam Rs - the tuple/array of input `Result` types.
|
|
749
|
+
* @typeParam Ts - per-element extracted success types (`OkOf` for `all`,
|
|
750
|
+
* `AsyncOkOf` for `allAsync`).
|
|
751
|
+
* @internal
|
|
853
752
|
*/
|
|
854
|
-
|
|
855
|
-
readonly tag: "Defect";
|
|
856
|
-
readonly cause: unknown;
|
|
857
|
-
}
|
|
753
|
+
type AllOk<Rs extends readonly unknown[], Ts extends readonly unknown[]> = number extends Rs["length"] ? Ts[number][] : Ts;
|
|
858
754
|
/**
|
|
859
|
-
* A
|
|
860
|
-
* {@link DefectView}. This is what a `tapFailure` callback receives — the
|
|
861
|
-
* discriminated variant rather than a payload, because the payload union
|
|
862
|
-
* `E | unknown` would collapse to `unknown` and lose `E`'s typing. Branch on
|
|
863
|
-
* `tag` to narrow (`"Err"` → `.error: E`, `"Defect"` → `.cause: unknown`).
|
|
755
|
+
* A `[key, error]` pair from a record aggregate, correlated per key.
|
|
864
756
|
*
|
|
865
757
|
* @remarks
|
|
866
|
-
*
|
|
867
|
-
*
|
|
868
|
-
*
|
|
758
|
+
* `-?` because the mapped type is homomorphic: an optional input key would
|
|
759
|
+
* otherwise carry its optionality through and put `undefined` in the entry
|
|
760
|
+
* union, breaking the documented `([key, error]) => …` destructure. An entry
|
|
761
|
+
* whose error channel is `never` (an infallible input — every `@unthrown/drizzle`
|
|
762
|
+
* read) is dropped rather than emitted as an uninhabited `[K, never]` arm, which
|
|
763
|
+
* a `switch` over the key would still have to write a dead case for.
|
|
764
|
+
*
|
|
765
|
+
* @internal
|
|
766
|
+
*/
|
|
767
|
+
type DictErrEntry<R> = { [K in keyof R]-?: [ErrOf<R[K]>] extends [never] ? never : readonly [K, ErrOf<R[K]>]; }[keyof R];
|
|
768
|
+
/** The {@link AsyncResult} counterpart of {@link DictErrEntry}. @internal */
|
|
769
|
+
type AsyncDictErrEntry<R> = { [K in keyof R]-?: [AsyncErrOf<R[K]>] extends [never] ? never : readonly [K, AsyncErrOf<R[K]>]; }[keyof R];
|
|
770
|
+
/** A non-empty readonly list — `merge` runs only when an `Err` was collected. @internal */
|
|
771
|
+
type NonEmpty<T> = readonly [T, ...T[]];
|
|
772
|
+
/**
|
|
773
|
+
* A record of `Result`s — the input to {@link allFromDict}. Keyed by any
|
|
774
|
+
* `PropertyKey`: a symbol key is folded like a string one (see
|
|
775
|
+
* {@link ownEntries}), so the constraint checks its value too.
|
|
776
|
+
*/
|
|
777
|
+
type ResultRecord = Record<PropertyKey, Result<unknown, unknown>>;
|
|
778
|
+
/** A record of `AsyncResult`s — the input to {@link allFromDictAsync}. */
|
|
779
|
+
type AsyncResultRecord = Record<PropertyKey, AsyncResult<unknown, unknown>>;
|
|
780
|
+
/**
|
|
781
|
+
* Collect a tuple/array of {@link Result}s into a single `Result` of all their
|
|
782
|
+
* success values.
|
|
783
|
+
*
|
|
784
|
+
* @remarks
|
|
785
|
+
* Short-circuits on the **first** `Err` (later entries are not inspected for
|
|
786
|
+
* their error); any `Defect` present **dominates**, winning even over an earlier
|
|
787
|
+
* `Err`. A **fixed tuple** keeps its positional types — `all([Ok(1), Ok("a")])`
|
|
788
|
+
* is `Result<[number, string], …>` — while a **dynamic array** `Result<T, E>[]`
|
|
789
|
+
* collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
|
|
790
|
+
* use {@link allFromDict}. To report **every** `Err` instead of only the first,
|
|
791
|
+
* use {@link validateAll}.
|
|
792
|
+
*
|
|
793
|
+
* @category Aggregate
|
|
869
794
|
*
|
|
870
795
|
* @example
|
|
871
796
|
* ```ts
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
797
|
+
* import { all, Ok, Err } from "unthrown";
|
|
798
|
+
*
|
|
799
|
+
* all([Ok(1), Ok("a"), Ok(true)]).get(); // => [1, "a", true] (typed [number, string, boolean])
|
|
800
|
+
* all([Ok(1), Err("e"), Ok(3)]); // => Err("e") (short-circuits on the first Err)
|
|
875
801
|
* ```
|
|
802
|
+
*/
|
|
803
|
+
export declare function all<Rs extends readonly Result<unknown, unknown>[]>(results: readonly [...Rs]): Result<AllOk<Rs, { [K in keyof Rs]: OkOf<Rs[K]>; }>, ErrOf<Rs[number]>>;
|
|
804
|
+
/**
|
|
805
|
+
* Collect a **record** of {@link Result}s into a single `Result` of a record of
|
|
806
|
+
* their success values — `allFromDict({ a: Result<A, E>, b: Result<B, E> })` is
|
|
807
|
+
* `Result<{ a: A; b: B }, E>`. The named counterpart of {@link all}, for
|
|
808
|
+
* parallel work you'd rather not tuple.
|
|
876
809
|
*
|
|
877
|
-
* @
|
|
878
|
-
*
|
|
879
|
-
*
|
|
810
|
+
* @remarks
|
|
811
|
+
* Same folding rules as {@link all}: first `Err` short-circuits, any `Defect`
|
|
812
|
+
* dominates. This is **not** error accumulation — for that, reach for
|
|
813
|
+
* {@link validateAllFromDict}, which accumulates every `Err` and folds them into
|
|
814
|
+
* one modeled error.
|
|
815
|
+
*
|
|
816
|
+
* @category Aggregate
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* ```ts
|
|
820
|
+
* import { allFromDict, Ok, Err } from "unthrown";
|
|
821
|
+
*
|
|
822
|
+
* allFromDict({ id: Ok(1), name: Ok("ada") }).get(); // => { id: 1, name: "ada" }
|
|
823
|
+
* allFromDict({ id: Ok(1), name: Err("missing") }); // => Err("missing")
|
|
824
|
+
* ```
|
|
880
825
|
*/
|
|
881
|
-
|
|
826
|
+
export declare function allFromDict<R extends ResultRecord>(results: R): Result<{ [K in keyof R]: OkOf<R[K]>; }, ErrOf<R[keyof R]>>;
|
|
882
827
|
/**
|
|
883
|
-
* The
|
|
884
|
-
*
|
|
828
|
+
* The asynchronous counterpart of {@link all}: combine a tuple/array of
|
|
829
|
+
* {@link AsyncResult}s into one `AsyncResult` of all their success values.
|
|
885
830
|
*
|
|
886
831
|
* @remarks
|
|
887
|
-
*
|
|
888
|
-
* `
|
|
832
|
+
* The inputs are resolved **concurrently** (order preserved); the resolved
|
|
833
|
+
* `Result`s are then folded with the same rules as {@link all} — first `Err`
|
|
834
|
+
* short-circuits, any `Defect` dominates. As ever, the returned `AsyncResult`'s
|
|
835
|
+
* internal promise never rejects. For a **record**, use {@link allFromDictAsync};
|
|
836
|
+
* to report **every** `Err`, use {@link validateAllAsync}.
|
|
889
837
|
*
|
|
890
|
-
*
|
|
891
|
-
* - **`Err`** — a modeled, anticipated failure carrying an `error: E`.
|
|
892
|
-
* - **`Defect`** — an *unmodeled* failure carrying an unknown `cause`. A Defect
|
|
893
|
-
* never appears in `E`; it is the library's third, out-of-band channel.
|
|
838
|
+
* @category Aggregate
|
|
894
839
|
*
|
|
895
|
-
*
|
|
896
|
-
*
|
|
897
|
-
*
|
|
898
|
-
*
|
|
899
|
-
*
|
|
840
|
+
* @example
|
|
841
|
+
* ```ts
|
|
842
|
+
* import { allAsync, fromSafePromise } from "unthrown";
|
|
843
|
+
*
|
|
844
|
+
* const both = allAsync([
|
|
845
|
+
* fromSafePromise(Promise.resolve(1)),
|
|
846
|
+
* fromSafePromise(Promise.resolve(2)),
|
|
847
|
+
* ]);
|
|
848
|
+
* (await both).get(); // => [1, 2]
|
|
849
|
+
* ```
|
|
850
|
+
*/
|
|
851
|
+
export declare function allAsync<Rs extends readonly AsyncResult<unknown, unknown>[]>(results: readonly [...Rs]): AsyncResult<AllOk<Rs, { [K in keyof Rs]: AsyncOkOf<Rs[K]>; }>, AsyncErrOf<Rs[number]>>;
|
|
852
|
+
/**
|
|
853
|
+
* The asynchronous counterpart of {@link allFromDict}: combine a record of
|
|
854
|
+
* {@link AsyncResult}s into one `AsyncResult` of a record of their values.
|
|
900
855
|
*
|
|
901
|
-
* @
|
|
902
|
-
*
|
|
856
|
+
* @remarks
|
|
857
|
+
* Resolved concurrently (order preserved), folded with the {@link all} rules,
|
|
858
|
+
* and the internal promise never rejects. To report **every** `Err`, use
|
|
859
|
+
* {@link validateAllFromDictAsync}.
|
|
860
|
+
*
|
|
861
|
+
* @category Aggregate
|
|
903
862
|
*
|
|
904
863
|
* @example
|
|
905
864
|
* ```ts
|
|
906
|
-
* import {
|
|
907
|
-
*
|
|
908
|
-
* function half(n: number): Result<number, "odd"> {
|
|
909
|
-
* return n % 2 === 0 ? Ok(n / 2) : Err("odd");
|
|
910
|
-
* }
|
|
865
|
+
* import { allFromDictAsync, fromSafePromise } from "unthrown";
|
|
911
866
|
*
|
|
912
|
-
* const
|
|
913
|
-
*
|
|
914
|
-
*
|
|
915
|
-
* errCases: (matcher) => matcher.with("odd", () => "failed: odd"),
|
|
916
|
-
* defect: (cause) => `bug: ${String(cause)}`,
|
|
867
|
+
* const both = allFromDictAsync({
|
|
868
|
+
* a: fromSafePromise(Promise.resolve(1)),
|
|
869
|
+
* b: fromSafePromise(Promise.resolve("x")),
|
|
917
870
|
* });
|
|
871
|
+
* (await both).get(); // => { a: 1, b: "x" }
|
|
918
872
|
* ```
|
|
919
873
|
*/
|
|
920
|
-
|
|
874
|
+
export declare function allFromDictAsync<R extends AsyncResultRecord>(results: R): AsyncResult<{ [K in keyof R]: AsyncOkOf<R[K]>; }, AsyncErrOf<R[keyof R]>>;
|
|
921
875
|
/**
|
|
922
|
-
*
|
|
923
|
-
*
|
|
876
|
+
* Collect a tuple/array of {@link Result}s, **accumulating every** `Err` and
|
|
877
|
+
* merging them into a single modeled error — the accumulating counterpart of
|
|
878
|
+
* {@link all}.
|
|
924
879
|
*
|
|
925
880
|
* @remarks
|
|
926
|
-
*
|
|
927
|
-
*
|
|
928
|
-
* channel
|
|
929
|
-
*
|
|
930
|
-
*
|
|
931
|
-
* rejects. What the narrowing prevents is treating it as a full promise:
|
|
932
|
-
* `.catch()` / `.finally()` do not type-check, because there is no rejection to
|
|
933
|
-
* handle.
|
|
881
|
+
* Same success channel as {@link all}: a **fixed tuple** keeps its positional
|
|
882
|
+
* types, a **dynamic array** collapses to `Result<T[], E2>`. The difference is
|
|
883
|
+
* the error channel — instead of the first `Err` winning, every `Err` is
|
|
884
|
+
* collected in input order and handed to `merge`, whose return becomes the
|
|
885
|
+
* modeled error.
|
|
934
886
|
*
|
|
935
|
-
*
|
|
887
|
+
* `merge` receives a **non-empty** list, so it is total: it is called only when
|
|
888
|
+
* at least one `Err` was collected. It is **not** called when every element is
|
|
889
|
+
* `Ok`, nor when a `Defect` is present.
|
|
936
890
|
*
|
|
937
|
-
*
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
* The async method surface every {@link AsyncResult} carries — the combinators
|
|
944
|
-
* (`map`, `flatMap`, `mapErrCases`, `match`, `get`, …) with their asynchronous
|
|
945
|
-
* signatures, documented one per entry below. The async mirror of
|
|
946
|
-
* {@link ResultMethods}: each entry links its synchronous counterpart and states
|
|
947
|
-
* only the async delta.
|
|
891
|
+
* Any `Defect` still **dominates** — it wins over the accumulated errors, which
|
|
892
|
+
* are discarded and never reach `merge`. A defect means something in this batch
|
|
893
|
+
* failed in a way nobody modeled, so the violations computed alongside it are
|
|
894
|
+
* not trustworthy. An out-of-contract non-`Result` element becomes a
|
|
895
|
+
* `TypeError`-caused `Defect` the same way, and a throw inside `merge` becomes
|
|
896
|
+
* a `Defect` too.
|
|
948
897
|
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
951
|
-
* to be authored against; you obtain it by holding an `AsyncResult`. Its
|
|
952
|
-
* combinator callbacks are **synchronous** (a raw `Promise` may never enter — see
|
|
953
|
-
* the {@link AsyncResult} remarks); async work re-enters via {@link fromPromise}
|
|
954
|
-
* and composes with `flatMap`. Systematic differences from the sync surface: the
|
|
955
|
-
* binds return an `AsyncResult` (and additionally accept one), and the
|
|
956
|
-
* eliminators return a `Promise`.
|
|
898
|
+
* `merge` must be **synchronous** — an `async` one is a compile error
|
|
899
|
+
* ({@link NotThenable}), since a `Promise` in `E` is an unqualified rejection.
|
|
957
900
|
*
|
|
958
|
-
*
|
|
959
|
-
*
|
|
960
|
-
*
|
|
901
|
+
* For **schema-shaped** input (a request body, a form), reach for
|
|
902
|
+
* `@unthrown/standard-schema`'s `fromSchema` instead — a validator already
|
|
903
|
+
* hands you every issue as the modeled error. `validateAll` is for independent
|
|
904
|
+
* checks you wrote yourself. For a **record** keyed by name, use
|
|
905
|
+
* {@link validateAllFromDict}.
|
|
906
|
+
*
|
|
907
|
+
* @typeParam Rs - the tuple/array of input `Result` types.
|
|
908
|
+
* @typeParam E2 - the merged error type.
|
|
909
|
+
* @param results - the results to collect.
|
|
910
|
+
* @param merge - folds the collected errors into one modeled error.
|
|
911
|
+
*
|
|
912
|
+
* @category Aggregate
|
|
913
|
+
*
|
|
914
|
+
* @example
|
|
915
|
+
* ```ts
|
|
916
|
+
* import { validateAll, Ok, Err } from "unthrown";
|
|
917
|
+
*
|
|
918
|
+
* // every Err is collected, not just the first
|
|
919
|
+
* validateAll([Ok(1), Err("stock"), Err("credit")], (errors) => errors.join(" and "));
|
|
920
|
+
* // => Err("stock and credit")
|
|
921
|
+
*
|
|
922
|
+
* // all-Ok keeps the positional tuple; `merge` never runs
|
|
923
|
+
* validateAll([Ok(1), Ok("a")], (errors) => errors.join());
|
|
924
|
+
* // => Ok([1, "a"]) typed Result<[number, string], string>
|
|
925
|
+
* ```
|
|
961
926
|
*/
|
|
962
|
-
|
|
963
|
-
/**
|
|
964
|
-
* Asynchronous {@link ResultMethods.map | map}: transforms the success value
|
|
965
|
-
* with `f`. `f` is synchronous; a throw becomes a `Defect`. An async callback
|
|
966
|
-
* is rejected at compile time ({@link NotThenable}).
|
|
967
|
-
*/
|
|
968
|
-
map<U>(f: (value: T) => U & NotThenable<U>): AsyncResult$1<U, E>;
|
|
969
|
-
/**
|
|
970
|
-
* Asynchronous {@link ResultMethods.flatMap | flatMap}. Unlike the sync form,
|
|
971
|
-
* `f` may return a `Result` **or** an `AsyncResult` (never a raw `Promise`); a
|
|
972
|
-
* throw becomes a `Defect`.
|
|
973
|
-
*
|
|
974
|
-
* @remarks
|
|
975
|
-
* The async branch of `f`'s return type is spelled `Awaitable<Result<U, E2>> &
|
|
976
|
-
* { flatMap: unknown }` rather than `AsyncResult<U, E2>`: this is what you get
|
|
977
|
-
* by returning an `AsyncResult` (it satisfies both), but inference runs through
|
|
978
|
-
* the `Awaitable` then-channel so `U`/`E2` stay precise instead of collapsing
|
|
979
|
-
* to `unknown`, while the `{ flatMap: unknown }` marker still rejects a bare
|
|
980
|
-
* `Promise` (it has no `flatMap`). Just return a `Result` or an `AsyncResult`.
|
|
981
|
-
*/
|
|
982
|
-
flatMap<U, E2>(f: (value: T) => Result$1<U, E2> | (Awaitable<Result$1<U, E2>> & {
|
|
983
|
-
flatMap: unknown;
|
|
984
|
-
})): AsyncResult$1<U, E | E2>;
|
|
985
|
-
/**
|
|
986
|
-
* Asynchronous {@link ResultMethods.tap | tap}. `f` is synchronous; a throw
|
|
987
|
-
* becomes a `Defect`. An async callback is rejected at compile time
|
|
988
|
-
* ({@link NotThenable}) — and so is a returned `AsyncResult` (it is
|
|
989
|
-
* awaitable). Beware the near-miss: _calling_ an `AsyncResult`-returning
|
|
990
|
-
* effect inside the callback without returning it compiles and leaves the
|
|
991
|
-
* effect floating — fire-and-forget, never awaited, its `Err`/`Defect`
|
|
992
|
-
* unobserved. If the effect returns a `Result`/`AsyncResult`, use
|
|
993
|
-
* {@link AsyncResultMethods.flatTap | flatTap}.
|
|
994
|
-
*/
|
|
995
|
-
tap<R>(f: (value: T) => R & NotThenable<R>): AsyncResult$1<T, E>;
|
|
996
|
-
/**
|
|
997
|
-
* Asynchronous {@link ResultMethods.flatTap | flatTap} — a failable tap that
|
|
998
|
-
* keeps the original value. `f` may return a `Result` **or** an `AsyncResult`;
|
|
999
|
-
* its `Ok` value is discarded, an `Err`/`Defect` short-circuits, and a throw
|
|
1000
|
-
* becomes a `Defect`.
|
|
1001
|
-
*/
|
|
1002
|
-
flatTap<E2>(f: (value: T) => Result$1<unknown, E2> | (Awaitable<Result$1<unknown, E2>> & {
|
|
1003
|
-
flatMap: unknown;
|
|
1004
|
-
})): AsyncResult$1<T, E | E2>;
|
|
1005
|
-
/**
|
|
1006
|
-
* Asynchronous {@link ResultMethods.bind | bind} (do-notation). `f` may return
|
|
1007
|
-
* a `Result` **or** an `AsyncResult`; its value is bound under `name` in the
|
|
1008
|
-
* accumulating scope.
|
|
1009
|
-
*/
|
|
1010
|
-
bind<K extends string, U, E2>(name: K, f: (scope: T) => Result$1<U, E2> | (Awaitable<Result$1<U, E2>> & {
|
|
1011
|
-
flatMap: unknown;
|
|
1012
|
-
})): AsyncResult$1<Bound<T, K, U>, E | E2>;
|
|
1013
|
-
/**
|
|
1014
|
-
* Asynchronous {@link ResultMethods.let | let} (do-notation). `f` returns a
|
|
1015
|
-
* plain value, bound under `name`. An async callback is rejected at compile
|
|
1016
|
-
* time ({@link NotThenable}).
|
|
1017
|
-
*/
|
|
1018
|
-
let<K extends string, U>(name: K, f: (scope: T) => U & NotThenable<U>): AsyncResult$1<Bound<T, K, U>, E>;
|
|
1019
|
-
/** Asynchronous {@link ResultMethods.as | as}: replaces the value with `value`. */
|
|
1020
|
-
as<U>(value: U): AsyncResult$1<U, E>;
|
|
1021
|
-
/** Asynchronous {@link ResultMethods.discard | discard}: drops the value, collapsing the success type to `void`. */
|
|
1022
|
-
discard(): AsyncResult$1<void, E>;
|
|
1023
|
-
/**
|
|
1024
|
-
* Asynchronous {@link ResultMethods.ensure | ensure}: validate the success
|
|
1025
|
-
* value — and, with a type-guard predicate (this overload), **refine** it —
|
|
1026
|
-
* failing into the modeled channel with `Err(onFail(value))`. Both callbacks
|
|
1027
|
-
* are synchronous (an async `onFail` is rejected at compile time,
|
|
1028
|
-
* {@link NotThenable}); a throw in either becomes a `Defect`.
|
|
1029
|
-
*/
|
|
1030
|
-
ensure<U extends T, E2>(predicate: (value: T) => value is U, onFail: (value: T) => E2 & NotThenable<E2>): AsyncResult$1<U, E | E2>;
|
|
1031
|
-
/** Boolean form of the asynchronous {@link ResultMethods.ensure | ensure} — validates without refining, keeping `T`. */
|
|
1032
|
-
ensure<E2>(predicate: (value: T) => boolean, onFail: (value: T) => E2 & NotThenable<E2>): AsyncResult$1<T, E | E2>;
|
|
1033
|
-
/**
|
|
1034
|
-
* Asynchronous {@link ResultMethods.mapErrCases | mapErrCases} — the same exhaustive
|
|
1035
|
-
* {@link ErrMatcher} form; the combinator calls `.exhaustive()`.
|
|
1036
|
-
*/
|
|
1037
|
-
mapErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): AsyncResult$1<T, MatchErrOut<M>>;
|
|
1038
|
-
/**
|
|
1039
|
-
* Asynchronous {@link ResultMethods.flatMapErrCases | flatMapErrCases} — the same
|
|
1040
|
-
* exhaustive {@link ErrMatcher} form. Unlike the sync form, a branch may
|
|
1041
|
-
* return a `Result` **or** an `AsyncResult`.
|
|
1042
|
-
*/
|
|
1043
|
-
flatMapErrCases<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>>>;
|
|
1044
|
-
/**
|
|
1045
|
-
* Asynchronous {@link ResultMethods.recoverErrCases | recoverErrCases} — the same
|
|
1046
|
-
* exhaustive {@link ErrMatcher} form. Branches are synchronous; a throw
|
|
1047
|
-
* becomes a `Defect`.
|
|
1048
|
-
*/
|
|
1049
|
-
recoverErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): AsyncResult$1<T | MatchErrOut<M>, never>;
|
|
1050
|
-
/**
|
|
1051
|
-
* Asynchronous {@link ResultMethods.tapErrCases | tapErrCases}. `f` is synchronous; if it
|
|
1052
|
-
* throws — or a branch returns the injected `defect(cause)` marker, the
|
|
1053
|
-
* expression-position form of a throw — the result is a `Defect` whose cause
|
|
1054
|
-
* is an `AggregateError` of `[thrown, original failure]` — observing a failure
|
|
1055
|
-
* never destroys it. An
|
|
1056
|
-
* async branch is rejected at compile time ({@link NotThenable} on the
|
|
1057
|
-
* builder output) — other branch results are discarded, so a rejected
|
|
1058
|
-
* `Promise` would float unobserved. The
|
|
1059
|
-
* {@link AsyncResultMethods.tap | tap} fire-and-forget caveat applies here
|
|
1060
|
-
* too — a failable effect belongs in
|
|
1061
|
-
* {@link AsyncResultMethods.flatTapErrCases | flatTapErrCases}.
|
|
1062
|
-
*/
|
|
1063
|
-
tapErrCases<R>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<R & NotThenable<R>>): AsyncResult$1<T, E>;
|
|
1064
|
-
/**
|
|
1065
|
-
* Asynchronous {@link ResultMethods.flatTapErrCases | flatTapErrCases} — the
|
|
1066
|
-
* error-channel mirror of `flatTap`. `f` may return a `Result` **or** an
|
|
1067
|
-
* `AsyncResult`; its `Ok` value is discarded, an `Err`/`Defect` from `f`
|
|
1068
|
-
* threads through, and if `f` throws — or a branch returns the injected
|
|
1069
|
-
* `defect(cause)` marker, the expression-position form of a throw — the result
|
|
1070
|
-
* is a `Defect` whose cause is an `AggregateError` of `[thrown, original
|
|
1071
|
-
* failure]` — observing a failure never destroys it.
|
|
1072
|
-
*/
|
|
1073
|
-
flatTapErrCases<E2>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<Result$1<unknown, E2> | AsyncResult$1<unknown, E2>>): AsyncResult$1<T, E | E2>;
|
|
1074
|
-
/**
|
|
1075
|
-
* Asynchronous {@link ResultMethods.recoverDefect | recoverDefect}. `f` may
|
|
1076
|
-
* return a `Result` or an `AsyncResult`.
|
|
1077
|
-
*/
|
|
1078
|
-
recoverDefect<U, E2>(f: (cause: unknown) => Result$1<U, E2> | AsyncResult$1<U, E2>): AsyncResult$1<T | U, E | E2>;
|
|
1079
|
-
/**
|
|
1080
|
-
* Asynchronous {@link ResultMethods.tapDefect | tapDefect}. If `f` throws, the
|
|
1081
|
-
* result is a `Defect` whose cause is an `AggregateError` of `[thrown,
|
|
1082
|
-
* original failure]` — observing a failure never destroys it. An async
|
|
1083
|
-
* callback is rejected at compile time ({@link NotThenable}).
|
|
1084
|
-
*/
|
|
1085
|
-
tapDefect<R>(f: (cause: unknown) => R & NotThenable<R>): AsyncResult$1<T, E>;
|
|
1086
|
-
/**
|
|
1087
|
-
* Asynchronous {@link ResultMethods.tapFailure | tapFailure} — the
|
|
1088
|
-
* cross-channel observer. `f` receives the narrowed failure variant
|
|
1089
|
-
* ({@link FailureView}); if it throws, the result is a `Defect` whose cause
|
|
1090
|
-
* is an `AggregateError` of `[thrown, original failure]` — observing a
|
|
1091
|
-
* failure never destroys it. An async callback is rejected at compile time
|
|
1092
|
-
* ({@link NotThenable}).
|
|
1093
|
-
*/
|
|
1094
|
-
tapFailure<R>(f: (failure: FailureView<E, T>) => R & NotThenable<R>): AsyncResult$1<T, E>;
|
|
1095
|
-
/**
|
|
1096
|
-
* Asynchronous {@link ResultMethods.match | match}. Handlers are synchronous
|
|
1097
|
-
* (the `errCases` handler returns an exhaustive {@link ErrMatcher} builder, no
|
|
1098
|
-
* `defect` helper); resolves to a `Promise` of the folded value.
|
|
1099
|
-
*/
|
|
1100
|
-
match<ROk, RDefect, M extends ExhaustiveMatch<unknown>>(cases: {
|
|
1101
|
-
ok: (value: T) => ROk;
|
|
1102
|
-
errCases: (matcher: ErrMatcher<E>) => M;
|
|
1103
|
-
defect: (cause: unknown) => RDefect;
|
|
1104
|
-
}): Promise<ROk | RDefect | MatchOut<M>>;
|
|
1105
|
-
/**
|
|
1106
|
-
* Asynchronous {@link ResultMethods.get | get}. Compiles only when the
|
|
1107
|
-
* error channel is empty (`this: AsyncResult<T, never>`); the returned promise
|
|
1108
|
-
* rejects on a `Defect` (rethrowing its cause).
|
|
1109
|
-
*/
|
|
1110
|
-
get(this: AsyncResult$1<T, never>): Promise<T>;
|
|
1111
|
-
/**
|
|
1112
|
-
* Asynchronous {@link ResultMethods.getErr | getErr}. Compiles only when
|
|
1113
|
-
* the success channel is empty (`this: AsyncResult<never, E>`); the returned
|
|
1114
|
-
* promise rejects on a `Defect` (rethrowing its cause).
|
|
1115
|
-
*/
|
|
1116
|
-
getErr(this: AsyncResult$1<never, E>): Promise<E>;
|
|
1117
|
-
/** Asynchronous {@link ResultMethods.getOr | getOr}. */
|
|
1118
|
-
getOr<U>(fallback: U): Promise<T | U>;
|
|
1119
|
-
/** Asynchronous {@link ResultMethods.getOrElse | getOrElse}. */
|
|
1120
|
-
getOrElse<U>(f: (error: E) => U): Promise<T | U>;
|
|
1121
|
-
/** Asynchronous {@link ResultMethods.getOrNull | getOrNull}. */
|
|
1122
|
-
getOrNull(): Promise<T | null>;
|
|
1123
|
-
/** Asynchronous {@link ResultMethods.getOrUndefined | getOrUndefined}. */
|
|
1124
|
-
getOrUndefined(): Promise<T | undefined>;
|
|
1125
|
-
/**
|
|
1126
|
-
* Asynchronous {@link ResultMethods.getOrThrow | getOrThrow} — the returned
|
|
1127
|
-
* promise **rejects** with the modeled error on `Err` (or the original cause
|
|
1128
|
-
* on a `Defect`), rather than throwing synchronously. Gated the same way: it
|
|
1129
|
-
* compiles only when the error channel is non-empty (`E` is not `never`).
|
|
1130
|
-
*/
|
|
1131
|
-
getOrThrow(this: [E] extends [never] ? "unthrown: getOrThrow is unnecessary here — the Err channel is empty (E = never), so there is nothing to throw. Use get() instead." : AsyncResult$1<T, E>): Promise<T>;
|
|
1132
|
-
};
|
|
927
|
+
export declare function validateAll<Rs extends readonly Result<unknown, unknown>[], E2>(results: readonly [...Rs], merge: (errors: NonEmpty<ErrOf<Rs[number]>>) => E2 & NotThenable<E2>): Result<AllOk<Rs, { [K in keyof Rs]: OkOf<Rs[K]>; }>, E2>;
|
|
1133
928
|
/**
|
|
1134
|
-
*
|
|
1135
|
-
*
|
|
1136
|
-
*
|
|
929
|
+
* Collect a **record** of {@link Result}s, accumulating every `Err` — the
|
|
930
|
+
* accumulating counterpart of {@link allFromDict}, and the named counterpart of
|
|
931
|
+
* {@link validateAll}.
|
|
1137
932
|
*
|
|
1138
933
|
* @remarks
|
|
1139
|
-
*
|
|
1140
|
-
*
|
|
1141
|
-
*
|
|
1142
|
-
*
|
|
1143
|
-
*
|
|
1144
|
-
*
|
|
1145
|
-
* (`flatMap`, `flatTap`, `flatMapErrCases`, `recoverDefect`) additionally accept an
|
|
1146
|
-
* `AsyncResult`. Its combinators are documented one per entry on
|
|
1147
|
-
* {@link AsyncResultMethods}.
|
|
934
|
+
* `merge` receives a non-empty list of **`[key, error]` entries**, correlated
|
|
935
|
+
* per key: `{ a: Result<A, E1>; b: Result<B, E2> }` yields
|
|
936
|
+
* `["a", E1] | ["b", E2]`, so a `switch` on the key narrows the error and an
|
|
937
|
+
* impossible pairing does not typecheck. That is what keeps two checks sharing
|
|
938
|
+
* one error type distinguishable. Entries come in key order — `Object.keys`
|
|
939
|
+
* order, then enumerable symbol keys (a symbol key is folded like any other).
|
|
1148
940
|
*
|
|
1149
|
-
*
|
|
941
|
+
* Every other rule matches {@link validateAll}: any `Defect` dominates and
|
|
942
|
+
* discards the accumulated errors, a throw in `merge` becomes a `Defect`, and
|
|
943
|
+
* `merge` must be synchronous.
|
|
1150
944
|
*
|
|
1151
|
-
* @typeParam
|
|
1152
|
-
* @typeParam
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
/**
|
|
1156
|
-
* Extract the success type `T` from a `Result` type — derive one type from
|
|
1157
|
-
* another instead of restating it (e.g. the payload a function returns).
|
|
945
|
+
* @typeParam R - the record of input `Result` types.
|
|
946
|
+
* @typeParam E2 - the merged error type.
|
|
947
|
+
* @param results - the results to collect, keyed by name.
|
|
948
|
+
* @param merge - folds the collected `[key, error]` entries into one error.
|
|
1158
949
|
*
|
|
1159
|
-
* @
|
|
950
|
+
* @category Aggregate
|
|
1160
951
|
*
|
|
1161
952
|
* @example
|
|
1162
953
|
* ```ts
|
|
1163
|
-
*
|
|
1164
|
-
* type U = OkOf<R>; // User
|
|
1165
|
-
* type E = ErrOf<R>; // NotFound
|
|
1166
|
-
* ```
|
|
954
|
+
* import { validateAllFromDict, Ok, Err } from "unthrown";
|
|
1167
955
|
*
|
|
1168
|
-
*
|
|
956
|
+
* validateAllFromDict(
|
|
957
|
+
* { vatRate: Err("out of range"), currency: Ok("EUR"), dueDate: Err("past") },
|
|
958
|
+
* (entries) => entries.map(([key, error]) => `${key}: ${error}`).join("; "),
|
|
959
|
+
* );
|
|
960
|
+
* // => Err("vatRate: out of range; dueDate: past")
|
|
961
|
+
* ```
|
|
1169
962
|
*/
|
|
1170
|
-
|
|
1171
|
-
readonly tag: "Ok";
|
|
1172
|
-
readonly value: infer T;
|
|
1173
|
-
} ? T : never;
|
|
963
|
+
export declare function validateAllFromDict<R extends ResultRecord, E2>(results: R, merge: (entries: NonEmpty<DictErrEntry<R>>) => E2 & NotThenable<E2>): Result<{ [K in keyof R]: OkOf<R[K]>; }, E2>;
|
|
1174
964
|
/**
|
|
1175
|
-
*
|
|
1176
|
-
* {@link
|
|
1177
|
-
*
|
|
1178
|
-
* @typeParam R - the `Result` type to inspect.
|
|
965
|
+
* The asynchronous counterpart of {@link validateAll}: collect a tuple/array of
|
|
966
|
+
* {@link AsyncResult}s, accumulating every `Err` into one merged error.
|
|
1179
967
|
*
|
|
1180
|
-
* @
|
|
1181
|
-
*
|
|
1182
|
-
*
|
|
1183
|
-
*
|
|
968
|
+
* @remarks
|
|
969
|
+
* Every {@link validateAll} rule holds, with the inputs resolved
|
|
970
|
+
* **concurrently** (order preserved) — as with {@link allAsync}, no work is
|
|
971
|
+
* short-circuited either way; the fail-fast/accumulating split is purely which
|
|
972
|
+
* errors get reported. The internal promise never rejects: an out-of-contract
|
|
973
|
+
* rejecting thenable becomes a dominating `Defect`. `merge` stays synchronous
|
|
974
|
+
* here too — this is exactly where its rejection would land unqualified in `E`.
|
|
975
|
+
* For a **record**, use {@link validateAllFromDictAsync}.
|
|
1184
976
|
*
|
|
1185
|
-
* @
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
readonly error: infer E;
|
|
1190
|
-
} ? E : never;
|
|
1191
|
-
/**
|
|
1192
|
-
* Extract the success type `T` from an {@link AsyncResult} type — the async
|
|
1193
|
-
* counterpart of {@link OkOf}.
|
|
977
|
+
* @typeParam Rs - the tuple/array of input `AsyncResult` types.
|
|
978
|
+
* @typeParam E2 - the merged error type.
|
|
979
|
+
* @param results - the async results to collect.
|
|
980
|
+
* @param merge - folds the collected errors into one modeled error.
|
|
1194
981
|
*
|
|
1195
|
-
* @
|
|
982
|
+
* @category Aggregate
|
|
1196
983
|
*
|
|
1197
984
|
* @example
|
|
1198
985
|
* ```ts
|
|
1199
|
-
*
|
|
1200
|
-
* ```
|
|
986
|
+
* import { validateAllAsync, OkAsync, ErrAsync } from "unthrown";
|
|
1201
987
|
*
|
|
1202
|
-
*
|
|
988
|
+
* const checked = validateAllAsync(
|
|
989
|
+
* [OkAsync(1), ErrAsync("stock"), ErrAsync("credit")],
|
|
990
|
+
* (errors) => errors.join(" and "),
|
|
991
|
+
* );
|
|
992
|
+
* // (await checked) => Err("stock and credit")
|
|
993
|
+
* ```
|
|
1203
994
|
*/
|
|
1204
|
-
|
|
995
|
+
export declare function validateAllAsync<Rs extends readonly AsyncResult<unknown, unknown>[], E2>(results: readonly [...Rs], merge: (errors: NonEmpty<AsyncErrOf<Rs[number]>>) => E2 & NotThenable<E2>): AsyncResult<AllOk<Rs, { [K in keyof Rs]: AsyncOkOf<Rs[K]>; }>, E2>;
|
|
1205
996
|
/**
|
|
1206
|
-
*
|
|
1207
|
-
*
|
|
997
|
+
* The asynchronous counterpart of {@link validateAllFromDict}: collect a record
|
|
998
|
+
* of {@link AsyncResult}s, accumulating every `Err` into one merged error.
|
|
1208
999
|
*
|
|
1209
|
-
* @
|
|
1000
|
+
* @remarks
|
|
1001
|
+
* The {@link validateAllFromDict} rules, over inputs resolved concurrently as
|
|
1002
|
+
* in {@link validateAllAsync}.
|
|
1003
|
+
*
|
|
1004
|
+
* @typeParam R - the record of input `AsyncResult` types.
|
|
1005
|
+
* @typeParam E2 - the merged error type.
|
|
1006
|
+
* @param results - the async results to collect, keyed by name.
|
|
1007
|
+
* @param merge - folds the collected `[key, error]` entries into one error.
|
|
1008
|
+
*
|
|
1009
|
+
* @category Aggregate
|
|
1210
1010
|
*
|
|
1211
1011
|
* @example
|
|
1212
1012
|
* ```ts
|
|
1213
|
-
*
|
|
1214
|
-
* ```
|
|
1013
|
+
* import { validateAllFromDictAsync, OkAsync, ErrAsync } from "unthrown";
|
|
1215
1014
|
*
|
|
1216
|
-
*
|
|
1015
|
+
* const checked = validateAllFromDictAsync(
|
|
1016
|
+
* { stock: ErrAsync("none left"), credit: OkAsync(500) },
|
|
1017
|
+
* (entries) => entries.map(([key, error]) => `${key}: ${error}`).join("; "),
|
|
1018
|
+
* );
|
|
1019
|
+
* // (await checked) => Err("stock: none left")
|
|
1020
|
+
* ```
|
|
1217
1021
|
*/
|
|
1218
|
-
|
|
1022
|
+
export declare function validateAllFromDictAsync<R extends AsyncResultRecord, E2>(results: R, merge: (entries: NonEmpty<AsyncDictErrEntry<R>>) => E2 & NotThenable<E2>): AsyncResult<{ [K in keyof R]: AsyncOkOf<R[K]>; }, E2>;
|
|
1219
1023
|
//#endregion
|
|
1220
|
-
//#region src/
|
|
1024
|
+
//#region src/facade.d.ts
|
|
1221
1025
|
/**
|
|
1222
|
-
*
|
|
1223
|
-
*
|
|
1224
|
-
*
|
|
1026
|
+
* Companion object grouping the **`Result`-producing** entry points under a
|
|
1027
|
+
* single, discoverable namespace: {@link Result.Ok}, {@link Result.Err},
|
|
1028
|
+
* {@link Result.Do}, {@link Result.fromNullable}, {@link Result.fromThrowable},
|
|
1029
|
+
* {@link Result.fromSafeThrowable}, {@link Result.all},
|
|
1030
|
+
* {@link Result.allFromDict}, {@link Result.validateAll},
|
|
1031
|
+
* {@link Result.validateAllFromDict}, {@link Result.isOk}, {@link Result.isErr},
|
|
1032
|
+
* {@link Result.isDefect}, {@link Result.isResult}.
|
|
1033
|
+
*
|
|
1034
|
+
* @remarks
|
|
1035
|
+
* Purely additive sugar — each member **is** the corresponding free function.
|
|
1036
|
+
* The free functions remain the primary, tree-shakeable API; importing only
|
|
1037
|
+
* `{ Ok }` never pulls this object in. The value `Result` and the type
|
|
1038
|
+
* {@link Result} share one name (the companion-object pattern).
|
|
1039
|
+
*
|
|
1040
|
+
* The **async** entry points live on the sibling {@link AsyncResult} companion
|
|
1041
|
+
* (`AsyncResult.fromPromise`, `AsyncResult.all`, …), grouped by what they
|
|
1042
|
+
* return — a static lives in exactly one namespace.
|
|
1043
|
+
*
|
|
1044
|
+
* @category Facade
|
|
1225
1045
|
*
|
|
1226
1046
|
* @example
|
|
1227
1047
|
* ```ts
|
|
1228
|
-
* import {
|
|
1229
|
-
*
|
|
1230
|
-
* Ok(); // => a void success: Result<void, never>
|
|
1048
|
+
* import { Result } from "unthrown";
|
|
1049
|
+
* Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).get(); // => 2
|
|
1231
1050
|
* ```
|
|
1232
|
-
*
|
|
1233
|
-
* @category Constructors
|
|
1234
1051
|
*/
|
|
1235
|
-
declare
|
|
1052
|
+
export declare const Result: {
|
|
1053
|
+
readonly Ok: typeof Ok;
|
|
1054
|
+
readonly Err: typeof Err;
|
|
1055
|
+
readonly Do: typeof Do;
|
|
1056
|
+
readonly fromNullable: typeof fromNullable;
|
|
1057
|
+
readonly fromThrowable: typeof fromThrowable;
|
|
1058
|
+
readonly fromSafeThrowable: typeof fromSafeThrowable;
|
|
1059
|
+
readonly all: typeof all;
|
|
1060
|
+
readonly allFromDict: typeof allFromDict;
|
|
1061
|
+
readonly validateAll: typeof validateAll;
|
|
1062
|
+
readonly validateAllFromDict: typeof validateAllFromDict;
|
|
1063
|
+
readonly isOk: typeof isOk;
|
|
1064
|
+
readonly isErr: typeof isErr;
|
|
1065
|
+
readonly isDefect: typeof isDefect;
|
|
1066
|
+
readonly isResult: typeof isResult;
|
|
1067
|
+
};
|
|
1236
1068
|
/**
|
|
1237
|
-
*
|
|
1069
|
+
* The core type of the library: a computation that has either succeeded with a
|
|
1070
|
+
* value of type `T` or failed with a *modeled* error of type `E`. Shares its
|
|
1071
|
+
* name with the {@link Result | companion object} above (the value and type are
|
|
1072
|
+
* one name); this is the type half.
|
|
1238
1073
|
*
|
|
1239
|
-
* @
|
|
1240
|
-
*
|
|
1074
|
+
* @remarks
|
|
1075
|
+
* A `Result` is a **discriminated union** of three variants, distinguished by a
|
|
1076
|
+
* `tag` of `"Ok"` | `"Err"` | `"Defect"`:
|
|
1241
1077
|
*
|
|
1242
|
-
*
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1078
|
+
* - **`Ok`** — a success carrying a `value: T`.
|
|
1079
|
+
* - **`Err`** — a modeled, anticipated failure carrying an `error: E`.
|
|
1080
|
+
* - **`Defect`** — an *unmodeled* failure carrying an unknown `cause`. A Defect
|
|
1081
|
+
* never appears in `E`; it is the library's third, out-of-band channel.
|
|
1245
1082
|
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1083
|
+
* Because it is a real union, you can match it natively (a `switch` on `tag`, or
|
|
1084
|
+
* the built-in `match(...).with({ tag: "Ok" }, …).exhaustive()`), *and* it
|
|
1085
|
+
* carries the full method surface for fluent chaining. Either way, the payload
|
|
1086
|
+
* (`value`/`error`/`cause`) is only reachable after you narrow — so "check
|
|
1087
|
+
* before you access" still holds.
|
|
1088
|
+
*
|
|
1089
|
+
* TypeDoc can't list a union's methods on this alias: its fluent combinators
|
|
1090
|
+
* (`map`, `flatMap`, `match`, `get`, …) are documented one per entry on
|
|
1091
|
+
* {@link ResultMethods} — the shared method surface every variant carries. For
|
|
1092
|
+
* "which one do I reach for?", see the
|
|
1093
|
+
* [Choosing a combinator](/reference/combinators) guide.
|
|
1249
1094
|
*
|
|
1250
|
-
* @
|
|
1251
|
-
|
|
1252
|
-
declare function Ok<T>(value: T): Result$1<T, never>;
|
|
1253
|
-
/**
|
|
1254
|
-
* Construct a failed {@link Result} carrying a **modeled** error.
|
|
1095
|
+
* @typeParam T - the success value type.
|
|
1096
|
+
* @typeParam E - the modeled error type (only anticipated domain failures).
|
|
1255
1097
|
*
|
|
1256
|
-
* @
|
|
1257
|
-
* @param error - the domain error to wrap.
|
|
1098
|
+
* @category Facade
|
|
1258
1099
|
*
|
|
1259
1100
|
* @example
|
|
1260
1101
|
* ```ts
|
|
1261
|
-
* import { Err } from "unthrown";
|
|
1102
|
+
* import { Ok, Err, type Result } from "unthrown";
|
|
1262
1103
|
*
|
|
1263
|
-
*
|
|
1264
|
-
* Err("
|
|
1265
|
-
*
|
|
1104
|
+
* function half(n: number): Result<number, "odd"> {
|
|
1105
|
+
* return n % 2 === 0 ? Ok(n / 2) : Err("odd");
|
|
1106
|
+
* }
|
|
1266
1107
|
*
|
|
1267
|
-
*
|
|
1108
|
+
* const message = half(10).match({
|
|
1109
|
+
* ok: (n) => `got ${n}`,
|
|
1110
|
+
* // every case of `E` named — here the one literal it holds
|
|
1111
|
+
* errCases: (matcher) => matcher.with("odd", () => "failed: odd"),
|
|
1112
|
+
* defect: (cause) => `bug: ${String(cause)}`,
|
|
1113
|
+
* });
|
|
1114
|
+
* ```
|
|
1268
1115
|
*/
|
|
1269
|
-
|
|
1116
|
+
export type Result<T, E> = OkView<T, E> | ErrView<E, T> | DefectView<T, E>;
|
|
1270
1117
|
/**
|
|
1271
|
-
*
|
|
1272
|
-
*
|
|
1273
|
-
*
|
|
1118
|
+
* Companion object grouping the **`AsyncResult`-producing** entry points under
|
|
1119
|
+
* the matching namespace: {@link AsyncResult.Ok}, {@link AsyncResult.Err},
|
|
1120
|
+
* {@link AsyncResult.Do}, {@link AsyncResult.fromExecutor},
|
|
1121
|
+
* {@link AsyncResult.fromPromise}, {@link AsyncResult.fromSafePromise},
|
|
1122
|
+
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict},
|
|
1123
|
+
* {@link AsyncResult.validateAll}, {@link AsyncResult.validateAllFromDict}.
|
|
1124
|
+
*
|
|
1125
|
+
* @remarks
|
|
1126
|
+
* The async sibling of {@link Result}. Statics are grouped by what they
|
|
1127
|
+
* **return**, so the pre-lifted constructors, `fromExecutor`,
|
|
1128
|
+
* `fromPromise`/`fromSafePromise`, and the async aggregates sit here rather
|
|
1129
|
+
* than on {@link Result}; the namespace
|
|
1130
|
+
* already conveys "async", so the members drop the `Async` suffix their free
|
|
1131
|
+
* functions carry (`AsyncResult.Ok` is `OkAsync`; `AsyncResult.Err` is
|
|
1132
|
+
* `ErrAsync`; `AsyncResult.Do` is `DoAsync`; `AsyncResult.all` is `allAsync`;
|
|
1133
|
+
* `AsyncResult.allFromDict` is
|
|
1134
|
+
* `allFromDictAsync`; `AsyncResult.validateAll` is `validateAllAsync`). Like
|
|
1135
|
+
* {@link Result}, the free functions remain the
|
|
1136
|
+
* primary, tree-shakeable API; the value `AsyncResult` and the type
|
|
1137
|
+
* {@link AsyncResult} share one name.
|
|
1138
|
+
*
|
|
1139
|
+
* @category Facade
|
|
1274
1140
|
*
|
|
1275
1141
|
* @example
|
|
1276
1142
|
* ```ts
|
|
1277
|
-
* import {
|
|
1278
|
-
*
|
|
1279
|
-
*
|
|
1143
|
+
* import { AsyncResult } from "unthrown";
|
|
1144
|
+
* const user = await AsyncResult.fromPromise(
|
|
1145
|
+
* fetchUser(id),
|
|
1146
|
+
* (c, defect) => defect(c),
|
|
1147
|
+
* );
|
|
1148
|
+
* user.get(); // => the fetched user (on success)
|
|
1280
1149
|
* ```
|
|
1281
|
-
*
|
|
1282
|
-
* @category Constructors
|
|
1283
1150
|
*/
|
|
1284
|
-
declare
|
|
1151
|
+
export declare const AsyncResult: {
|
|
1152
|
+
readonly Ok: typeof OkAsync;
|
|
1153
|
+
readonly Err: typeof ErrAsync;
|
|
1154
|
+
readonly Do: typeof DoAsync;
|
|
1155
|
+
readonly fromExecutor: typeof fromExecutor;
|
|
1156
|
+
readonly fromPromise: typeof fromPromise;
|
|
1157
|
+
readonly fromSafePromise: typeof fromSafePromise;
|
|
1158
|
+
readonly all: typeof allAsync;
|
|
1159
|
+
readonly allFromDict: typeof allFromDictAsync;
|
|
1160
|
+
readonly validateAll: typeof validateAllAsync;
|
|
1161
|
+
readonly validateAllFromDict: typeof validateAllFromDictAsync;
|
|
1162
|
+
};
|
|
1285
1163
|
/**
|
|
1286
|
-
*
|
|
1287
|
-
*
|
|
1164
|
+
* The asynchronous counterpart of {@link Result}: an awaitable wrapper carrying
|
|
1165
|
+
* the {@link AsyncResultMethods} surface, collapsing to a `Result<T, E>` when
|
|
1166
|
+
* `await`-ed. Shares its name with the {@link AsyncResult | companion object}
|
|
1167
|
+
* above (value and type are one name); this is the type half.
|
|
1168
|
+
*
|
|
1169
|
+
* @remarks
|
|
1170
|
+
* **Combinator callbacks are synchronous.** A raw `Promise` may never enter an
|
|
1171
|
+
* `AsyncResult` method — that would be an un-qualified async boundary, and its
|
|
1172
|
+
* rejection would silently become a `Defect`, skipping the triage that
|
|
1173
|
+
* {@link fromPromise} forces. To do further async work, re-enter through a
|
|
1174
|
+
* qualified boundary and compose it: `ar.flatMap((v) => fromPromise(work(v),
|
|
1175
|
+
* qualify))`. The eliminators (`get`, …) return promises; the binds
|
|
1176
|
+
* (`flatMap`, `flatTap`, `flatMapErrCases`, `recoverDefect`) additionally accept an
|
|
1177
|
+
* `AsyncResult`. Its combinators are documented one per entry — with their
|
|
1178
|
+
* async signatures — on {@link AsyncResultMethods}. For "which one do I reach
|
|
1179
|
+
* for?", see the [Choosing a combinator](/reference/combinators) guide.
|
|
1288
1180
|
*
|
|
1289
|
-
*
|
|
1290
|
-
* Reach for this on the synchronous/early branch of an `AsyncResult`-returning
|
|
1291
|
-
* function, so both branches share one return type without a trailing
|
|
1292
|
-
* `.toAsync()`. Named with the `Async` suffix the async free functions carry
|
|
1293
|
-
* (`allAsync`, `allFromDictAsync`); the {@link AsyncResult} companion aliases it
|
|
1294
|
-
* as `AsyncResult.Ok` (the namespace already says "async", so the suffix drops).
|
|
1181
|
+
* To pattern-match an `AsyncResult`, `await` it first: `match(await ar)`.
|
|
1295
1182
|
*
|
|
1296
1183
|
* @typeParam T - the success value type.
|
|
1297
|
-
* @
|
|
1184
|
+
* @typeParam E - the modeled error type.
|
|
1298
1185
|
*
|
|
1299
|
-
* @
|
|
1300
|
-
|
|
1301
|
-
|
|
1186
|
+
* @category Facade
|
|
1187
|
+
*/
|
|
1188
|
+
export interface AsyncResult<out T, out E> extends Awaitable<Result<T, E>>, AsyncResultMethods<T, E> {}
|
|
1189
|
+
//#endregion
|
|
1190
|
+
//#region src/types.d.ts
|
|
1191
|
+
/**
|
|
1192
|
+
* Flatten an intersection into a single object literal so accumulated `bind` /
|
|
1193
|
+
* `let` scopes display cleanly (`{ a; b }` rather than `{ a } & { b }`).
|
|
1302
1194
|
*
|
|
1303
|
-
*
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
*
|
|
1195
|
+
* @internal
|
|
1196
|
+
*/
|
|
1197
|
+
type Prettify<T> = { [K in keyof T]: T[K]; } & {};
|
|
1198
|
+
/**
|
|
1199
|
+
* The scope produced by a `bind` / `let` step: `T` with `K` added (as a readonly
|
|
1200
|
+
* property of type `U`). `Omit<T, K>` first drops any existing `K`, so re-binding
|
|
1201
|
+
* a name **overwrites** it — matching the runtime spread — rather than producing
|
|
1202
|
+
* an unsound `T[K] & U` intersection.
|
|
1308
1203
|
*
|
|
1309
|
-
* @
|
|
1204
|
+
* @internal
|
|
1310
1205
|
*/
|
|
1311
|
-
|
|
1206
|
+
type Bound<T, K extends string, U> = Prettify<Omit<T, K> & { readonly [P in K]: U; }>;
|
|
1312
1207
|
/**
|
|
1313
|
-
*
|
|
1314
|
-
*
|
|
1208
|
+
* Compile-time rejection of a thenable callback result — the type-level
|
|
1209
|
+
* enforcement of "combinator callbacks are synchronous" (see the
|
|
1210
|
+
* {@link AsyncResult} remarks).
|
|
1315
1211
|
*
|
|
1316
1212
|
* @remarks
|
|
1317
|
-
*
|
|
1318
|
-
* `
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
1321
|
-
*
|
|
1322
|
-
*
|
|
1323
|
-
* @example
|
|
1324
|
-
* ```ts
|
|
1325
|
-
* import { ErrAsync } from "unthrown";
|
|
1213
|
+
* Resolves to `unknown` (a no-op in an intersection) for any non-thenable `R`,
|
|
1214
|
+
* and to an explanatory string-literal type when `R` is a `PromiseLike` — so an
|
|
1215
|
+
* `async` callback fails to compile with the explanation in the error. Without
|
|
1216
|
+
* this, `async () => …` would be assignable to `() => void`, and its rejection
|
|
1217
|
+
* would escape the pipeline as an unhandled rejection instead of a `Defect`.
|
|
1218
|
+
* Lift async work with {@link fromPromise} and compose it with `flatMap`.
|
|
1326
1219
|
*
|
|
1327
|
-
*
|
|
1328
|
-
*
|
|
1220
|
+
* Spelled with `Extract`, not `[R] extends [PromiseLike<…>]`, so the ban also
|
|
1221
|
+
* fires when only SOME arms of a union return are thenable — a *sometimes*-async
|
|
1222
|
+
* callback (`flag ? 1 : work()`) is still an unawaited effect whose rejection
|
|
1223
|
+
* the pipeline never sees. The tuple-wrapped form is false for a partial union
|
|
1224
|
+
* and let exactly that through. This is the same reasoning `fromPromise`'s
|
|
1225
|
+
* async-qualify guard already used.
|
|
1329
1226
|
*
|
|
1330
|
-
* @
|
|
1227
|
+
* @typeParam R - the callback's inferred return type.
|
|
1228
|
+
* @category Types
|
|
1331
1229
|
*/
|
|
1332
|
-
|
|
1230
|
+
type NotThenable<R> = [Extract<R, PromiseLike<unknown>>] extends [never] ? unknown : "unthrown: combinator callbacks are synchronous — lift async work with fromPromise and compose with flatMap";
|
|
1333
1231
|
/**
|
|
1334
|
-
*
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1337
|
-
*
|
|
1338
|
-
* @example
|
|
1339
|
-
* ```ts
|
|
1340
|
-
* import { isOk, Ok, Err, type Result } from "unthrown";
|
|
1341
|
-
*
|
|
1342
|
-
* isOk(Ok(1)); // => true
|
|
1343
|
-
* isOk(Err("boom")); // => false
|
|
1232
|
+
* The built-in match builder over an error union `E`, as produced by
|
|
1233
|
+
* `match(error)`. This is what an error combinator's callback receives — chain
|
|
1234
|
+
* `.with(pattern, handler)` on it; the combinator itself calls `.exhaustive()`,
|
|
1235
|
+
* so the callback returns the **un-terminated** builder.
|
|
1344
1236
|
*
|
|
1345
|
-
*
|
|
1346
|
-
*
|
|
1347
|
-
*
|
|
1237
|
+
* @remarks
|
|
1238
|
+
* Named via `ReturnType<typeof match<E>>` (i.e. `Matcher<E, E, never>`),
|
|
1239
|
+
* keeping this alias stable however the builder evolves.
|
|
1348
1240
|
*
|
|
1349
|
-
* @
|
|
1241
|
+
* @typeParam E - the error union being matched.
|
|
1242
|
+
* @category Types
|
|
1350
1243
|
*/
|
|
1351
|
-
|
|
1244
|
+
type ErrMatcher<E> = ReturnType<typeof match<E>>;
|
|
1352
1245
|
/**
|
|
1353
|
-
*
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1356
|
-
*
|
|
1357
|
-
*
|
|
1358
|
-
* ```ts
|
|
1359
|
-
* import { isErr, Ok, Err, type Result } from "unthrown";
|
|
1246
|
+
* The shape an error-combinator callback must return: an **exhaustive**
|
|
1247
|
+
* match builder. `exhaustive` is required to be *callable* — on a builder
|
|
1248
|
+
* that hasn't covered every case the matcher types it as a branded diagnostic
|
|
1249
|
+
* (not a function), so a non-exhaustive chain fails to satisfy this and errors
|
|
1250
|
+
* at the call site. `run` carries the output type.
|
|
1360
1251
|
*
|
|
1361
|
-
*
|
|
1362
|
-
*
|
|
1252
|
+
* @typeParam O - the union of the branch return types (the builder's output).
|
|
1253
|
+
* @internal
|
|
1254
|
+
*/
|
|
1255
|
+
type ExhaustiveMatch<O> = {
|
|
1256
|
+
exhaustive: (...args: never[]) => unknown;
|
|
1257
|
+
run: () => O;
|
|
1258
|
+
};
|
|
1259
|
+
/**
|
|
1260
|
+
* The output of an `ExhaustiveMatch` — the union of its branch returns.
|
|
1363
1261
|
*
|
|
1364
|
-
*
|
|
1365
|
-
|
|
1366
|
-
|
|
1262
|
+
* @internal
|
|
1263
|
+
*/
|
|
1264
|
+
type MatchOut<M> = M extends ExhaustiveMatch<infer O> ? O : never;
|
|
1265
|
+
/**
|
|
1266
|
+
* The outgoing modeled-error type a transforming match produces: the builder's
|
|
1267
|
+
* output with the `Defect` arm **subtracted** (`Exclude<O, Defect>`, the same
|
|
1268
|
+
* inference as the boundary `qualify`, Thesis #3). A branch that returns
|
|
1269
|
+
* `defect(cause)` therefore contributes nothing to the modeled channel.
|
|
1367
1270
|
*
|
|
1368
|
-
* @
|
|
1271
|
+
* @internal
|
|
1369
1272
|
*/
|
|
1370
|
-
|
|
1273
|
+
type MatchErrOut<M> = Exclude<MatchOut<M>, Defect>;
|
|
1371
1274
|
/**
|
|
1372
|
-
*
|
|
1275
|
+
* The phantom rest-tuple guard that bans an **async branch** in the
|
|
1276
|
+
* non-awaiting error transformers (`mapErrCases` / `recoverErrCases`): empty
|
|
1277
|
+
* for a synchronous builder output, an impossible extra argument (labelled
|
|
1278
|
+
* with the explanation) when any branch returns a thenable.
|
|
1373
1279
|
*
|
|
1374
1280
|
* @remarks
|
|
1375
|
-
*
|
|
1376
|
-
*
|
|
1377
|
-
*
|
|
1378
|
-
*
|
|
1379
|
-
*
|
|
1380
|
-
*
|
|
1381
|
-
*
|
|
1382
|
-
*
|
|
1383
|
-
*
|
|
1384
|
-
*
|
|
1385
|
-
*
|
|
1386
|
-
*
|
|
1387
|
-
*
|
|
1388
|
-
*
|
|
1389
|
-
*
|
|
1390
|
-
*
|
|
1391
|
-
*
|
|
1392
|
-
*
|
|
1281
|
+
* Those two run the matched branch without awaiting it, so an `async` branch
|
|
1282
|
+
* put `Err(<Promise>)` / `Ok(<Promise>)` in the channel — a `Promise` in `E` is
|
|
1283
|
+
* the un-triaged value Thesis #3 forbids — and its rejection floated
|
|
1284
|
+
* unobserved.
|
|
1285
|
+
*
|
|
1286
|
+
* The first test, `[O] extends [Pass]`, is the **generic escape**: `Pass` is
|
|
1287
|
+
* what the receiver's own channels already hold (`E` for `mapErrCases`,
|
|
1288
|
+
* `T | E` for `recoverErrCases`), so re-emitting the error — the sanctioned
|
|
1289
|
+
* `P._` use inside a helper generic in `E` — resolves to `[]` even while `E` is
|
|
1290
|
+
* an unresolved type parameter. No `NotThenable`-style check can: TypeScript
|
|
1291
|
+
* cannot decide "is not thenable" for an unresolved `E`, so `M &
|
|
1292
|
+
* NotThenable<…>` (the `tapErrCases` spelling) rejected that helper outright.
|
|
1293
|
+
* A thenable already in `E` is no new hole. Encoded as a trailing phantom
|
|
1294
|
+
* (the `fromPromise` shape) rather than on the callback's return, which keeps
|
|
1295
|
+
* the inference-bearing callback free of conditional types. A `this` gate
|
|
1296
|
+
* would print the message more directly but breaks the verified `out T, out E`
|
|
1297
|
+
* variance annotations (`Pass` mentions both). An `any` output (a mocked
|
|
1298
|
+
* branch) is let through, as `U & NotThenable<U>` does.
|
|
1393
1299
|
*
|
|
1394
|
-
* @
|
|
1300
|
+
* @internal
|
|
1395
1301
|
*/
|
|
1396
|
-
|
|
1397
|
-
//#endregion
|
|
1398
|
-
//#region src/core.d.ts
|
|
1302
|
+
type SyncBranches<O, Pass> = [O] extends [Pass | Defect] ? [] : 0 extends 1 & O ? [] : [Extract<O, PromiseLike<unknown>>] extends [never] ? [] : [unthrown_errorMatcherBranchesAreSynchronous: "an async branch would put a Promise in the channel — lift async work with fromPromise and use flatMapErrCases"];
|
|
1399
1303
|
/**
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
1402
|
-
* `
|
|
1304
|
+
* The marker that keeps a bare `Promise` out of the async binds (`flatMap` /
|
|
1305
|
+
* `flatTap` / `bind`) and the awaiting error combinators (`flatMapErrCases` /
|
|
1306
|
+
* `flatTapErrCases`): every `AsyncResult` has a `flatMap`, a `Promise` has
|
|
1307
|
+
* none, so a raw rejection cannot bypass qualification.
|
|
1403
1308
|
*
|
|
1404
1309
|
* @remarks
|
|
1405
|
-
*
|
|
1406
|
-
*
|
|
1407
|
-
*
|
|
1408
|
-
*
|
|
1310
|
+
* Structurally it is just `{ flatMap: unknown }`; the **name** is the
|
|
1311
|
+
* diagnostic. An `async` callback fails with "Property 'flatMap' is missing in
|
|
1312
|
+
* type 'Promise<…>' but required in type 'ReturnAnAsyncResultNotAPromise'",
|
|
1313
|
+
* which says what to do — instead of the anonymous `{ flatMap: unknown }`, or
|
|
1314
|
+
* (in the error combinators, which named `AsyncResult` itself) a list of 25
|
|
1315
|
+
* missing methods. It always sits beside `Awaitable<Result<…>>`, so inference
|
|
1316
|
+
* runs through the then-channel and stays junk-free (see
|
|
1317
|
+
* {@link AsyncResultMethods.flatMap}).
|
|
1409
1318
|
*
|
|
1410
|
-
*
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
*
|
|
1319
|
+
* @internal
|
|
1320
|
+
*/
|
|
1321
|
+
type ReturnAnAsyncResultNotAPromise = {
|
|
1322
|
+
flatMap: unknown;
|
|
1323
|
+
};
|
|
1324
|
+
/**
|
|
1325
|
+
* The fluent method surface every {@link Result} variant carries — the
|
|
1326
|
+
* combinators (`map`, `flatMap`, `mapErrCases`, `match`, `get`, …), documented one
|
|
1327
|
+
* per entry below. Factored out so the three variants ({@link OkView},
|
|
1328
|
+
* {@link ErrView}, {@link DefectView}) can each intersect it; {@link AsyncResult}
|
|
1329
|
+
* mirrors this surface with async signatures.
|
|
1417
1330
|
*
|
|
1418
|
-
* @
|
|
1331
|
+
* @remarks
|
|
1332
|
+
* This type exists to **document** the surface and to power narrowing — not to be
|
|
1333
|
+
* authored against. You obtain it by holding a `Result` (or `AsyncResult`), never
|
|
1334
|
+
* by implementing your own `Result`-like; treat it as read-only reference.
|
|
1419
1335
|
*
|
|
1420
|
-
* @
|
|
1336
|
+
* @typeParam T - the success value type.
|
|
1337
|
+
* @typeParam E - the modeled error type.
|
|
1338
|
+
* @category Methods
|
|
1421
1339
|
*/
|
|
1422
|
-
|
|
1340
|
+
type ResultMethods<out T, out E> = {
|
|
1341
|
+
/**
|
|
1342
|
+
* Transform the success value with `f`.
|
|
1343
|
+
*
|
|
1344
|
+
* Runs `f` only on `Ok`; `Err` and `Defect` pass through untouched. If `f`
|
|
1345
|
+
* throws, the thrown value is captured as a `Defect`.
|
|
1346
|
+
*
|
|
1347
|
+
* An async callback is rejected at compile time ({@link NotThenable}).
|
|
1348
|
+
*
|
|
1349
|
+
* @typeParam U - the mapped success type.
|
|
1350
|
+
* @param f - maps the current success value to a new one.
|
|
1351
|
+
*/
|
|
1352
|
+
map<U>(f: (value: T) => U & NotThenable<U>): Result<U, E>;
|
|
1353
|
+
/**
|
|
1354
|
+
* Sequence a dependent, `Result`-returning step (monadic bind).
|
|
1355
|
+
*
|
|
1356
|
+
* Runs `f` only on `Ok`; `Err` and `Defect` pass through. The error channels
|
|
1357
|
+
* combine, widening to `E | E2`. If `f` throws, the throw becomes a `Defect`.
|
|
1358
|
+
*
|
|
1359
|
+
* @typeParam U - the success type of the next step.
|
|
1360
|
+
* @typeParam E2 - the error type the next step may introduce.
|
|
1361
|
+
* @param f - produces the next `Result` from the current success value.
|
|
1362
|
+
*/
|
|
1363
|
+
flatMap<U, E2>(f: (value: T) => Result<U, E2>): Result<U, E | E2>;
|
|
1364
|
+
/**
|
|
1365
|
+
* Run a side effect on the success value and pass the `Result` through
|
|
1366
|
+
* unchanged.
|
|
1367
|
+
*
|
|
1368
|
+
* Runs only on `Ok`. If `f` throws, the throw becomes a `Defect`. An async
|
|
1369
|
+
* callback is rejected at compile time ({@link NotThenable}).
|
|
1370
|
+
*
|
|
1371
|
+
* @remarks
|
|
1372
|
+
* `f`'s return value is **ignored** — a `Result` returned by the effect
|
|
1373
|
+
* compiles but is discarded, `Err` and all. If the effect can fail, sequence
|
|
1374
|
+
* it instead of tapping it: a `Result`-returning effect goes in
|
|
1375
|
+
* {@link ResultMethods.flatTap | flatTap}; an `AsyncResult`-returning effect
|
|
1376
|
+
* cannot be sequenced from the sync surface — lift the chain with
|
|
1377
|
+
* {@link ResultMethods.toAsync | toAsync} and use the async
|
|
1378
|
+
* {@link AsyncResultMethods.flatTap | flatTap} (which accepts both).
|
|
1379
|
+
*
|
|
1380
|
+
* @param f - the side effect (its return value is ignored).
|
|
1381
|
+
*/
|
|
1382
|
+
tap<R>(f: (value: T) => R & NotThenable<R>): Result<T, E>;
|
|
1383
|
+
/**
|
|
1384
|
+
* Run a **failable** side effect on the success value, keeping the original
|
|
1385
|
+
* value but threading the effect's error.
|
|
1386
|
+
*
|
|
1387
|
+
* @remarks
|
|
1388
|
+
* This is to {@link ResultMethods.tap | tap} what
|
|
1389
|
+
* {@link ResultMethods.flatMap | flatMap} is to {@link ResultMethods.map | map}:
|
|
1390
|
+
* `f` returns a `Result`, but its **success value is discarded** — on success
|
|
1391
|
+
* the original value flows through (`Result<T, E | E2>`), while an `Err` (or
|
|
1392
|
+
* `Defect`) from `f` short-circuits. Runs only on `Ok`; `Err` and `Defect` pass
|
|
1393
|
+
* through. If `f` throws, the throw becomes a `Defect`. Use it for a validation
|
|
1394
|
+
* or write whose _result_ matters but whose _value_ you don't need.
|
|
1395
|
+
*
|
|
1396
|
+
* @typeParam E2 - the error type the effect may introduce.
|
|
1397
|
+
* @param f - the failable side effect; its `Ok` value is ignored.
|
|
1398
|
+
*/
|
|
1399
|
+
flatTap<E2>(f: (value: T) => Result<unknown, E2>): Result<T, E | E2>;
|
|
1400
|
+
/**
|
|
1401
|
+
* Do-notation: run `f` for a `Result` and **bind its value** under `name` in
|
|
1402
|
+
* an accumulating object scope.
|
|
1403
|
+
*
|
|
1404
|
+
* @remarks
|
|
1405
|
+
* Begin a chain with {@link Do} (an empty object scope) and grow it step by
|
|
1406
|
+
* step. `f` receives the scope accumulated so far and returns a `Result`; on
|
|
1407
|
+
* `Ok` the value is added as `{ ...scope, [name]: value }`, on `Err`/`Defect`
|
|
1408
|
+
* the chain short-circuits. Errors union (`E | E2`). A throw becomes a
|
|
1409
|
+
* `Defect` — as does calling `bind` on a non-object or non-plain scope (e.g.
|
|
1410
|
+
* `Ok(5).bind`, or a class instance whose getters the merge would drop), which
|
|
1411
|
+
* is misuse: the scope is always a plain object inside a real `Do()` chain.
|
|
1412
|
+
* (`let` is the pure-value counterpart.)
|
|
1413
|
+
*
|
|
1414
|
+
* @typeParam K - the key the bound value is stored under.
|
|
1415
|
+
* @typeParam U - the bound value type.
|
|
1416
|
+
* @typeParam E2 - the error type `f` may introduce.
|
|
1417
|
+
* @param name - the scope key.
|
|
1418
|
+
* @param f - produces a `Result` from the accumulated scope.
|
|
1419
|
+
*/
|
|
1420
|
+
bind<K extends string, U, E2>(name: K, f: (scope: T) => Result<U, E2>): Result<Bound<T, K, U>, E | E2>;
|
|
1421
|
+
/**
|
|
1422
|
+
* Do-notation: run `f` for a **plain value** and bind it under `name` in the
|
|
1423
|
+
* accumulating object scope. The pure-value counterpart of {@link ResultMethods.bind | bind}.
|
|
1424
|
+
*
|
|
1425
|
+
* @remarks
|
|
1426
|
+
* `f` receives the scope and returns a value (not a `Result`); it is added as
|
|
1427
|
+
* `{ ...scope, [name]: value }`. Runs only on `Ok`; `Err`/`Defect` pass
|
|
1428
|
+
* through. A throw becomes a `Defect`. An async callback is rejected at
|
|
1429
|
+
* compile time ({@link NotThenable}).
|
|
1430
|
+
*
|
|
1431
|
+
* @typeParam K - the key the value is stored under.
|
|
1432
|
+
* @typeParam U - the value type.
|
|
1433
|
+
* @param name - the scope key.
|
|
1434
|
+
* @param f - computes a value from the accumulated scope.
|
|
1435
|
+
*/
|
|
1436
|
+
let<K extends string, U>(name: K, f: (scope: T) => U & NotThenable<U>): Result<Bound<T, K, U>, E>;
|
|
1437
|
+
/**
|
|
1438
|
+
* Replace the success value with a constant `value`.
|
|
1439
|
+
*
|
|
1440
|
+
* Runs only on `Ok`; `Err` and `Defect` pass through.
|
|
1441
|
+
*
|
|
1442
|
+
* @typeParam U - the replacement value type.
|
|
1443
|
+
*/
|
|
1444
|
+
as<U>(value: U): Result<U, E>;
|
|
1445
|
+
/**
|
|
1446
|
+
* Drop the success value, collapsing the success type to `void`.
|
|
1447
|
+
*
|
|
1448
|
+
* The named form of `map(() => undefined)`. Runs only on `Ok` (the value is
|
|
1449
|
+
* replaced with `undefined`); `Err` and `Defect` pass through. Unlike
|
|
1450
|
+
* `as(undefined)` — which produces `Result<undefined, E>` — the success type
|
|
1451
|
+
* is `void`: the value's story ends here.
|
|
1452
|
+
*/
|
|
1453
|
+
discard(): Result<void, E>;
|
|
1454
|
+
/**
|
|
1455
|
+
* Validate the success value — keep the `Ok` when `predicate` holds,
|
|
1456
|
+
* otherwise fail into the **modeled** channel with `Err(onFail(value))`.
|
|
1457
|
+
*
|
|
1458
|
+
* @remarks
|
|
1459
|
+
* The named form of `flatMap((v) => (p(v) ? Ok(v) : Err(e)))`. With a
|
|
1460
|
+
* **type-guard** predicate (`(v): v is U`) the success type is **refined** to
|
|
1461
|
+
* `U` on the way through (this overload). Runs only on `Ok` — a passing value
|
|
1462
|
+
* flows through as the *same* `Ok`; `Err` and `Defect` pass through
|
|
1463
|
+
* untouched. A throw in `predicate` or `onFail` becomes a `Defect`.
|
|
1464
|
+
*
|
|
1465
|
+
* Both callbacks are synchronous: an async `onFail` is rejected at compile
|
|
1466
|
+
* time ({@link NotThenable}), and an async predicate does not type-check
|
|
1467
|
+
* either — its `Promise<boolean>` is not a `boolean` (and, being truthy,
|
|
1468
|
+
* would have silently always passed).
|
|
1469
|
+
*
|
|
1470
|
+
* @typeParam U - the refined success type (type-guard form).
|
|
1471
|
+
* @typeParam E2 - the error type `onFail` produces.
|
|
1472
|
+
* @param predicate - the check; a type guard refines `T` to `U`.
|
|
1473
|
+
* @param onFail - maps the failing value to the modeled error.
|
|
1474
|
+
*
|
|
1475
|
+
* @example
|
|
1476
|
+
* ```ts
|
|
1477
|
+
* // boolean form: gate a value
|
|
1478
|
+
* Ok(-1).ensure((n) => n > 0, (n) => `negative: ${n}`); // Err("negative: -1")
|
|
1479
|
+
*
|
|
1480
|
+
* // type-guard form: refine the success type
|
|
1481
|
+
* declare const r: Result<string | number, "e">;
|
|
1482
|
+
* const s = r.ensure(
|
|
1483
|
+
* (v): v is string => typeof v === "string",
|
|
1484
|
+
* () => "not_a_string" as const,
|
|
1485
|
+
* ); // Result<string, "e" | "not_a_string">
|
|
1486
|
+
* ```
|
|
1487
|
+
*/
|
|
1488
|
+
ensure<U extends T, E2>(predicate: (value: T) => value is U, onFail: (value: T) => E2 & NotThenable<E2>): Result<U, E | E2>;
|
|
1489
|
+
/**
|
|
1490
|
+
* Boolean form of {@link ResultMethods.ensure | ensure} — validates without
|
|
1491
|
+
* refining, keeping the success type `T`.
|
|
1492
|
+
*/
|
|
1493
|
+
ensure<E2>(predicate: (value: T) => boolean, onFail: (value: T) => E2 & NotThenable<E2>): Result<T, E | E2>;
|
|
1494
|
+
/**
|
|
1495
|
+
* Transform the modeled error by **matching it exhaustively**.
|
|
1496
|
+
*
|
|
1497
|
+
* @remarks
|
|
1498
|
+
* The callback receives `match(error)` (an {@link ErrMatcher}) and the
|
|
1499
|
+
* injected `defect` helper. Chain `.with(pattern, handler)` and **return the
|
|
1500
|
+
* un-terminated builder** — `mapErrCases` calls `.exhaustive()` itself, so a
|
|
1501
|
+
* missing case is a compile error at the call site (there is no `.exhaustive()`
|
|
1502
|
+
* to forget, and no way to slip in `.otherwise()`). The outgoing error type is
|
|
1503
|
+
* the union of the branch returns with the `Defect` arm subtracted
|
|
1504
|
+
* (`Exclude<O, Defect>`) — a branch returning `defect(cause)` converts that case
|
|
1505
|
+
* to a `Defect` and drops it from `E`. Runs only on `Err`; `Ok` and `Defect`
|
|
1506
|
+
* pass through. A branch that throws also becomes a `Defect`. Branches are
|
|
1507
|
+
* **synchronous**: an `async` branch is a compile error (its `Promise` would
|
|
1508
|
+
* land in `E` un-triaged), and a thenable slipped past the types becomes a
|
|
1509
|
+
* `Defect`.
|
|
1510
|
+
*
|
|
1511
|
+
* **Name every case.** Match on anything the matcher supports — `_tag`,
|
|
1512
|
+
* `code`, structural shape, guards — and group the cases that share a handler
|
|
1513
|
+
* with `.with(a, b, handler)`. `.with(P._, …)` is the wildcard **escape
|
|
1514
|
+
* hatch**, not the default: it makes any match exhaustive, so it also absorbs
|
|
1515
|
+
* every case `E` grows later. Two uses are sanctioned — a helper generic in
|
|
1516
|
+
* `E`, where no arm list can prove exhaustiveness against an unresolved type
|
|
1517
|
+
* parameter, and an `E` that is a single type rather than a union of cases
|
|
1518
|
+
* (see {@link P} for both). `@unthrown/oxlint`'s `no-catch-all-pattern` (in
|
|
1519
|
+
* its `recommended` preset) flags the rest.
|
|
1520
|
+
*
|
|
1521
|
+
* @typeParam M - the exhaustive builder the callback returns.
|
|
1522
|
+
* @param f - builds the match over the error (returns the un-terminated builder).
|
|
1523
|
+
* @param _asyncBranchBanned_liftWithFromPromiseThenFlatMapErrCases - compile-time
|
|
1524
|
+
* only; never pass it. Empty for synchronous branches; an **async** branch
|
|
1525
|
+
* demands this impossible argument, so the call fails to compile (its name is
|
|
1526
|
+
* the fix).
|
|
1527
|
+
*/
|
|
1528
|
+
mapErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M, ..._asyncBranchBanned_liftWithFromPromiseThenFlatMapErrCases: SyncBranches<MatchOut<M>, E>): Result<T, MatchErrOut<M>>;
|
|
1529
|
+
/**
|
|
1530
|
+
* Sequence from an `Err` by producing another `Result` — the error-channel
|
|
1531
|
+
* mirror of {@link ResultMethods.flatMap | flatMap}, **matching the error
|
|
1532
|
+
* exhaustively** ({@link ErrMatcher}; the combinator calls `.exhaustive()`).
|
|
1533
|
+
*
|
|
1534
|
+
* Each branch returns a `Result`; the outgoing channels are the unions of the
|
|
1535
|
+
* branch-returned `Result`s' channels. A branch may return `defect(cause)`.
|
|
1536
|
+
* Runs only on `Err`; `Ok` and `Defect` pass through.
|
|
1537
|
+
*
|
|
1538
|
+
* @typeParam M - the exhaustive builder the callback returns.
|
|
1539
|
+
* @param f - builds the match; each branch produces a fallback `Result`.
|
|
1540
|
+
*/
|
|
1541
|
+
flatMapErrCases<M extends ExhaustiveMatch<Result<unknown, unknown> | Defect>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): Result<T | OkOf<MatchOut<M>>, ErrOf<MatchOut<M>>>;
|
|
1542
|
+
/**
|
|
1543
|
+
* Recover from an `Err` by producing a success value, emptying the error
|
|
1544
|
+
* channel — **matching the error exhaustively** ({@link ErrMatcher}). Pairs
|
|
1545
|
+
* with {@link ResultMethods.recoverDefect | recoverDefect}.
|
|
1546
|
+
*
|
|
1547
|
+
* @remarks
|
|
1548
|
+
* The result type is `Result<T | U, never>`, but `never` describes only the
|
|
1549
|
+
* **error** channel — a `Defect` can still be present at runtime. A branch may
|
|
1550
|
+
* return `defect(cause)` (which stays a `Defect`, not a recovery). Runs only on
|
|
1551
|
+
* `Err`; `Ok` and `Defect` pass through. Branches are **synchronous**: an
|
|
1552
|
+
* `async` branch is a compile error, and a thenable slipped past the types
|
|
1553
|
+
* becomes a `Defect`.
|
|
1554
|
+
*
|
|
1555
|
+
* @typeParam M - the exhaustive builder the callback returns.
|
|
1556
|
+
* @param f - builds the match; each branch produces a success value.
|
|
1557
|
+
* @param _asyncBranchBanned_liftWithFromPromiseThenFlatMapErrCases - compile-time
|
|
1558
|
+
* only; never pass it. Empty for synchronous branches; an **async** branch
|
|
1559
|
+
* demands this impossible argument, so the call fails to compile (its name is
|
|
1560
|
+
* the fix).
|
|
1561
|
+
*/
|
|
1562
|
+
recoverErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M, ..._asyncBranchBanned_liftWithFromPromiseThenFlatMapErrCases: SyncBranches<MatchOut<M>, T | E>): Result<T | MatchErrOut<M>, never>;
|
|
1563
|
+
/**
|
|
1564
|
+
* Run a side effect on the error — **matched exhaustively** ({@link ErrMatcher})
|
|
1565
|
+
* — and pass the `Result` through unchanged.
|
|
1566
|
+
*
|
|
1567
|
+
* @remarks
|
|
1568
|
+
* The callback builds a match whose branches run side effects; their return
|
|
1569
|
+
* values are ignored and the original `Err` flows through. Exhaustive like the
|
|
1570
|
+
* transformers, and like them it wants every case named — `.with(P._, …)`
|
|
1571
|
+
* remains the wildcard escape hatch. If a branch throws, the
|
|
1572
|
+
* result is a `Defect` whose cause is an `AggregateError` of `[thrown, original
|
|
1573
|
+
* failure]` — observing a failure never destroys it. An **async branch is
|
|
1574
|
+
* rejected at compile time** ({@link NotThenable} on the builder output):
|
|
1575
|
+
* because the branch results are discarded, a returned `Promise` would float
|
|
1576
|
+
* unobserved and its rejection would vanish. The one branch return that is
|
|
1577
|
+
* **not** discarded is the injected `defect(cause)` marker: it is the
|
|
1578
|
+
* lint-clean, expression-position form of a `throw`, so it follows the throw
|
|
1579
|
+
* rule above (an `AggregateError` of `[the branch's cause, original
|
|
1580
|
+
* failure]`), never a silent no-op. A failable
|
|
1581
|
+
* `Result`-returning effect belongs in
|
|
1582
|
+
* {@link ResultMethods.flatTapErrCases | flatTapErrCases}.
|
|
1583
|
+
*
|
|
1584
|
+
* @param f - builds the match; branch returns are ignored, bar `defect(cause)`.
|
|
1585
|
+
*/
|
|
1586
|
+
tapErrCases<R>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<R & NotThenable<R>>): Result<T, E>;
|
|
1587
|
+
/**
|
|
1588
|
+
* Run a **failable** side effect on the error, keeping the original error but
|
|
1589
|
+
* threading the effect's own error — **matched exhaustively**
|
|
1590
|
+
* ({@link ErrMatcher}).
|
|
1591
|
+
*
|
|
1592
|
+
* @remarks
|
|
1593
|
+
* The error-channel mirror of {@link ResultMethods.flatTap | flatTap}: each
|
|
1594
|
+
* branch returns a `Result` whose **success value is discarded** — on the
|
|
1595
|
+
* effect's `Ok` the original `Err` flows through, while an `Err`/`Defect` from a
|
|
1596
|
+
* branch short-circuits and threads its error. Note the asymmetry with a
|
|
1597
|
+
* *throw*: a branch that **returns** a Defect-state `Result` **replaces** the
|
|
1598
|
+
* original `Err` (Defect-dominance, the short-circuit rule — it is not
|
|
1599
|
+
* aggregated), whereas a branch that **throws** produces a `Defect`
|
|
1600
|
+
* aggregating `[thrown, original failure]` (observing a failure by throwing
|
|
1601
|
+
* never destroys it). A branch returning the injected `defect(cause)` marker —
|
|
1602
|
+
* reachable under a `returnType` pin — follows the *throw* rule, since it is
|
|
1603
|
+
* the lint-clean, expression-position form of one.
|
|
1604
|
+
*
|
|
1605
|
+
* @typeParam M - the exhaustive builder the callback returns.
|
|
1606
|
+
* @param f - builds the match; each branch is a failable effect (its `Ok` is ignored).
|
|
1607
|
+
*/
|
|
1608
|
+
flatTapErrCases<E2>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<Result<unknown, E2>>): Result<T, E | E2>;
|
|
1609
|
+
/**
|
|
1610
|
+
* Recover from a `Defect` — the **only** combinator that can touch one.
|
|
1611
|
+
*
|
|
1612
|
+
* @remarks
|
|
1613
|
+
* Runs `f` only when a `Defect` is present, re-entering the modeled world by
|
|
1614
|
+
* returning a `Result` (an `Ok` or a fresh `Err`). `Ok` and `Err` pass
|
|
1615
|
+
* through. Recovering a Defect should be rare: usually you let it bubble to
|
|
1616
|
+
* the edge. If `f` throws, the throw becomes a new `Defect`.
|
|
1617
|
+
*
|
|
1618
|
+
* @typeParam U - a success type the recovery may produce.
|
|
1619
|
+
* @typeParam E2 - an error type the recovery may produce.
|
|
1620
|
+
* @param f - maps the Defect's unknown cause to a recovering `Result`.
|
|
1621
|
+
*/
|
|
1622
|
+
recoverDefect<U, E2>(f: (cause: unknown) => Result<U, E2>): Result<T | U, E | E2>;
|
|
1623
|
+
/**
|
|
1624
|
+
* Run a side effect on a present `Defect`'s cause (e.g. logging) and pass the
|
|
1625
|
+
* `Defect` through unchanged. If `f` throws, the result is a `Defect` whose
|
|
1626
|
+
* cause is an `AggregateError` of `[thrown, original failure]` — observing a
|
|
1627
|
+
* failure never destroys it. An async callback is rejected at compile time
|
|
1628
|
+
* ({@link NotThenable}).
|
|
1629
|
+
*
|
|
1630
|
+
* @param f - the side effect over the unknown cause.
|
|
1631
|
+
*/
|
|
1632
|
+
tapDefect<R>(f: (cause: unknown) => R & NotThenable<R>): Result<T, E>;
|
|
1633
|
+
/**
|
|
1634
|
+
* Run a side effect on **any failure** — `Err` or `Defect` — and pass the
|
|
1635
|
+
* `Result` through unchanged. The one cross-channel observer, for the shared
|
|
1636
|
+
* "it went KO" concern (logging, metrics, rollback) that would otherwise be
|
|
1637
|
+
* duplicated across {@link ResultMethods.tapErrCases | tapErrCases} and
|
|
1638
|
+
* {@link ResultMethods.tapDefect | tapDefect}.
|
|
1639
|
+
*
|
|
1640
|
+
* @remarks
|
|
1641
|
+
* `f` receives the narrowed **failure variant** ({@link FailureView}), not a
|
|
1642
|
+
* payload — the payload union `E | unknown` would collapse to `unknown` and
|
|
1643
|
+
* lose `E`'s typing. Branch on `failure.tag` to reach the typed payload
|
|
1644
|
+
* (`"Err"` → `failure.error: E`, `"Defect"` → `failure.cause: unknown`), or
|
|
1645
|
+
* treat it opaquely for a shared logger. Runs on `Err` and `Defect`; `Ok`
|
|
1646
|
+
* passes through. It **observes without consuming**: the failure flows on
|
|
1647
|
+
* unchanged — to also recover, use
|
|
1648
|
+
* {@link ResultMethods.recoverErrCases | recoverErrCases} /
|
|
1649
|
+
* {@link ResultMethods.recoverDefect | recoverDefect} (deliberately separate
|
|
1650
|
+
* acts) or {@link ResultMethods.match | match} at the edge. If `f` throws, the
|
|
1651
|
+
* result is a `Defect` whose cause is an `AggregateError` of `[thrown,
|
|
1652
|
+
* original failure]` — observing a failure never destroys it. An async
|
|
1653
|
+
* callback is rejected at compile time ({@link NotThenable}).
|
|
1654
|
+
*
|
|
1655
|
+
* @param f - the side effect over the failure variant (its return value is ignored).
|
|
1656
|
+
*/
|
|
1657
|
+
tapFailure<R>(f: (failure: FailureView<E, T>) => R & NotThenable<R>): Result<T, E>;
|
|
1658
|
+
/**
|
|
1659
|
+
* Exhaustively fold all three runtime states into a single value.
|
|
1660
|
+
*
|
|
1661
|
+
* @remarks
|
|
1662
|
+
* Exactly one handler runs. Together with the throw-to-Defect guarantee, this
|
|
1663
|
+
* is typically the single place a pipeline is handled at the edge — mapping
|
|
1664
|
+
* `Ok`/`Err`/`Defect` to (for example) 2xx / 4xx / 5xx with no `try`/`catch`.
|
|
1665
|
+
*
|
|
1666
|
+
* The `errCases` handler does not take a single blanket callback: it receives
|
|
1667
|
+
* `match(error)` (an {@link ErrMatcher}) and **matches the error exhaustively**,
|
|
1668
|
+
* exactly like the error combinators — which is why the key carries the same
|
|
1669
|
+
* `…Cases` suffix. Chain `.with(pattern, handler)` and **return the
|
|
1670
|
+
* un-terminated builder** — `match` calls `.exhaustive()` itself, so a missing
|
|
1671
|
+
* case is a compile error at the call site (no `.exhaustive()` to forget).
|
|
1672
|
+
* Folding at the edge names every case too — `.with(P._, …)` is the wildcard
|
|
1673
|
+
* escape hatch, not the default. Unlike the combinators the branches
|
|
1674
|
+
* receive **no `defect` helper** — `match` is total elimination to a value,
|
|
1675
|
+
* with no `Defect` output channel; the `defect` case handles a `Result` that
|
|
1676
|
+
* already carries one. (A `Result` is also a discriminated union — for richer
|
|
1677
|
+
* whole-`Result` matching, `match(result).with(…)`.)
|
|
1678
|
+
*
|
|
1679
|
+
* @typeParam ROk - the `ok` handler return type.
|
|
1680
|
+
* @typeParam RDefect - the `defect` handler return type.
|
|
1681
|
+
* @typeParam M - the exhaustive builder the `errCases` handler returns.
|
|
1682
|
+
* @param cases - the `ok`/`defect` handlers plus the `errCases` matcher builder.
|
|
1683
|
+
*/
|
|
1684
|
+
match<ROk, RDefect, M extends ExhaustiveMatch<unknown>>(cases: {
|
|
1685
|
+
ok: (value: T) => ROk;
|
|
1686
|
+
errCases: (matcher: ErrMatcher<E>) => M;
|
|
1687
|
+
defect: (cause: unknown) => RDefect;
|
|
1688
|
+
}): ROk | RDefect | MatchOut<M>;
|
|
1689
|
+
/**
|
|
1690
|
+
* Extract the success value.
|
|
1691
|
+
*
|
|
1692
|
+
* @remarks
|
|
1693
|
+
* Compiles only when the error channel is empty (`E = never`) — eliminate
|
|
1694
|
+
* modeled errors first (`match` / `recoverErrCases` / `flatMapErrCases`), or reach for the
|
|
1695
|
+
* `getOr` / `getOrElse` / `getOrNull` / `getOrUndefined` family (which
|
|
1696
|
+
* recover an `Err`). The gate is a `this` type that becomes an explanatory
|
|
1697
|
+
* string when `E` is not `never`, so the compile error names the fix.
|
|
1698
|
+
*
|
|
1699
|
+
* `E = never` empties only the **modeled** error channel — a `Defect` can
|
|
1700
|
+
* still be present, and `get()` **rethrows its original cause** (it
|
|
1701
|
+
* _panics_); `Result<T, never>` does not mean `get()` cannot throw.
|
|
1702
|
+
*
|
|
1703
|
+
* @returns the `Ok` value.
|
|
1704
|
+
*/
|
|
1705
|
+
get(this: [E] extends [never] ? Result<T, never> : "unthrown: get() needs an empty error channel (E = never) — handle the Err first with recoverErrCases / match / flatMapErrCases, or use getOr / getOrElse / getOrNull / getOrUndefined"): T;
|
|
1423
1706
|
/**
|
|
1424
|
-
*
|
|
1425
|
-
*
|
|
1707
|
+
* Extract the modeled error.
|
|
1708
|
+
*
|
|
1709
|
+
* @remarks
|
|
1710
|
+
* Compiles only when the success channel is empty (`T = never`) — eliminate
|
|
1711
|
+
* the success case first. `T = never` is rarely the case in practice (a
|
|
1712
|
+
* `Result` you hold usually still has a success type), so to inspect an
|
|
1713
|
+
* error prefer an `isErr()` guard or, in tests, `@unthrown/vitest`'s
|
|
1714
|
+
* `toBeErrWith`. A `Defect` still **rethrows its original cause** (a defect is
|
|
1715
|
+
* a bug, not an absent value), so this does not mean `getErr()` can't throw.
|
|
1716
|
+
*
|
|
1717
|
+
* @returns the `Err` value.
|
|
1426
1718
|
*/
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1719
|
+
getErr(this: [T] extends [never] ? Result<never, E> : "unthrown: getErr() needs an empty success channel (T = never) — narrow with isErr() first, or fold with match"): E;
|
|
1720
|
+
/**
|
|
1721
|
+
* The success value, or `fallback` on `Err`.
|
|
1722
|
+
*
|
|
1723
|
+
* @typeParam U - the fallback type (may differ from `T`; the return widens to `T | U`).
|
|
1724
|
+
* @param fallback - returned when the result is an `Err` (may be a different type; the return widens to `T | U`).
|
|
1725
|
+
* @throws Re-throws on a `Defect` — a Defect is a bug, not an absent value, so
|
|
1726
|
+
* it is never silently replaced.
|
|
1727
|
+
*/
|
|
1728
|
+
getOr<U>(fallback: U): T | U;
|
|
1729
|
+
/**
|
|
1730
|
+
* The success value, or `f(error)` on `Err`.
|
|
1731
|
+
*
|
|
1732
|
+
* @typeParam U - the fallback type (may differ from `T`; the return widens to `T | U`).
|
|
1733
|
+
* @param f - lazily computes the fallback from the error (may return a different type; the return widens to `T | U`).
|
|
1734
|
+
* @throws Re-throws on a `Defect`.
|
|
1735
|
+
*/
|
|
1736
|
+
getOrElse<U>(f: (error: E) => U): T | U;
|
|
1737
|
+
/**
|
|
1738
|
+
* The success value, or `null` on `Err`.
|
|
1739
|
+
*
|
|
1740
|
+
* @throws Re-throws on a `Defect`.
|
|
1741
|
+
*/
|
|
1742
|
+
getOrNull(): T | null;
|
|
1743
|
+
/**
|
|
1744
|
+
* The success value, or `undefined` on `Err`.
|
|
1745
|
+
*
|
|
1746
|
+
* @throws Re-throws on a `Defect`.
|
|
1747
|
+
*/
|
|
1748
|
+
getOrUndefined(): T | undefined;
|
|
1749
|
+
/**
|
|
1750
|
+
* The success value, or **throw** the modeled error on `Err`.
|
|
1751
|
+
*
|
|
1752
|
+
* @remarks
|
|
1753
|
+
* A deliberate escape hatch off the errors-as-values model — it **throws the
|
|
1754
|
+
* `Err` value as-is** at the call site, so a caller of the enclosing function
|
|
1755
|
+
* sees a throw rather than a channel. Its home is **tests and scripts**,
|
|
1756
|
+
* where "this `Result` had better be `Ok`" is the assertion and a throw is
|
|
1757
|
+
* the correct failure mode.
|
|
1758
|
+
*
|
|
1759
|
+
* In production code, fold the error channel instead:
|
|
1760
|
+
* {@link ResultMethods.recoverErrCases | recoverErrCases} empties `E`, so
|
|
1761
|
+
* {@link ResultMethods.get | get} compiles and a case routed to the injected
|
|
1762
|
+
* `defect(...)` panics with its original cause — with every case still named.
|
|
1763
|
+
* {@link ResultMethods.match | match} and
|
|
1764
|
+
* {@link ResultMethods.flatMapErrCases | flatMapErrCases} are the other two
|
|
1765
|
+
* ways to keep the error a value. `@unthrown/oxlint`'s opt-in
|
|
1766
|
+
* `no-get-or-throw` rule enforces this, exempting test files through an
|
|
1767
|
+
* oxlint `overrides` entry.
|
|
1768
|
+
*
|
|
1769
|
+
* Type-gated as the **complement** of {@link ResultMethods.get | get}: it
|
|
1770
|
+
* compiles only when the error channel is **non-empty** (`E` is not `never`) —
|
|
1771
|
+
* there must be a modeled error for it to throw. On a `Result<T, never>` there
|
|
1772
|
+
* is nothing to throw, so `getOrThrow` does not compile; use `get()` (which
|
|
1773
|
+
* gates the other way). Together they partition extraction by the error
|
|
1774
|
+
* channel's state, with no overlap.
|
|
1775
|
+
*
|
|
1776
|
+
* @returns the `Ok` value.
|
|
1777
|
+
* @throws the modeled `error` on `Err`; re-throws the original `cause` on a
|
|
1778
|
+
* `Defect` (a panic, like the rest of the `getOr…` family).
|
|
1779
|
+
*/
|
|
1780
|
+
getOrThrow(this: [E] extends [never] ? "unthrown: getOrThrow is unnecessary here — the Err channel is empty (E = never), so there is nothing to throw. Use get() instead." : Result<T, E>): T;
|
|
1781
|
+
/** Whether this result is `Ok` — narrows `this` to its {@link OkView} on `true`. */
|
|
1782
|
+
isOk(): this is OkView<T, E>;
|
|
1783
|
+
/** Whether this result is `Err` — narrows `this` to its {@link ErrView} on `true`. */
|
|
1784
|
+
isErr(): this is ErrView<E, T>;
|
|
1785
|
+
/** Whether this result is a `Defect` — narrows `this` to its {@link DefectView} on `true`. */
|
|
1786
|
+
isDefect(): this is DefectView<T, E>;
|
|
1787
|
+
/** Lift this synchronous `Result` into an {@link AsyncResult}. */
|
|
1788
|
+
toAsync(): AsyncResult<T, E>;
|
|
1789
|
+
};
|
|
1470
1790
|
/**
|
|
1471
|
-
*
|
|
1472
|
-
* `
|
|
1473
|
-
*
|
|
1474
|
-
* @remarks
|
|
1475
|
-
* Capitalised because `do` is a reserved word. Each step receives the scope
|
|
1476
|
-
* accumulated so far; the error types union across `bind`s, and a throw in any
|
|
1477
|
-
* step becomes a `Defect`. To go asynchronous, lift the chain with `toAsync()`
|
|
1478
|
-
* (then a `bind` may return an `AsyncResult`).
|
|
1479
|
-
*
|
|
1480
|
-
* @example
|
|
1481
|
-
* ```ts
|
|
1482
|
-
* import { Do, Ok } from "unthrown";
|
|
1483
|
-
*
|
|
1484
|
-
* const result = Do()
|
|
1485
|
-
* .bind("user", () => findUser(id)) // Result<User, NotFound>
|
|
1486
|
-
* .bind("org", ({ user }) => findOrg(user.orgId)) // Result<Org, NotFound>
|
|
1487
|
-
* .let("label", ({ user, org }) => `${user.name} @ ${org.name}`)
|
|
1488
|
-
* .map(({ user, org, label }) => render(user, org, label));
|
|
1489
|
-
* // Result<View, NotFound>
|
|
1490
|
-
* ```
|
|
1791
|
+
* The `Ok` variant of a {@link Result}: a success carrying a `value`. This is
|
|
1792
|
+
* what a successful `isOk` guard narrows to, making `.value` reachable. It also
|
|
1793
|
+
* carries the shared fluent surface ({@link ResultMethods}).
|
|
1491
1794
|
*
|
|
1492
1795
|
* @example
|
|
1493
1796
|
* ```ts
|
|
1494
|
-
*
|
|
1495
|
-
*
|
|
1496
|
-
* // Ok path — the scope accumulates:
|
|
1497
|
-
* Do()
|
|
1498
|
-
* .bind("a", () => Ok(2))
|
|
1499
|
-
* .let("b", ({ a }) => a * 10)
|
|
1500
|
-
* .map(({ a, b }) => a + b); // => Ok(22)
|
|
1501
|
-
*
|
|
1502
|
-
* // Err path — the first Err short-circuits the rest:
|
|
1503
|
-
* Do()
|
|
1504
|
-
* .bind("a", () => Err("boom"))
|
|
1505
|
-
* .let("b", ({ a }) => a); // => Err("boom")
|
|
1797
|
+
* if (r.isOk()) r.value; // r: OkView<T, E> here — .value is a T
|
|
1506
1798
|
* ```
|
|
1507
1799
|
*
|
|
1508
|
-
* @category
|
|
1800
|
+
* @category Types
|
|
1509
1801
|
*/
|
|
1510
|
-
|
|
1802
|
+
interface OkView<out T, out E = never> extends ResultMethods<T, E> {
|
|
1803
|
+
readonly tag: "Ok";
|
|
1804
|
+
readonly value: T;
|
|
1805
|
+
}
|
|
1511
1806
|
/**
|
|
1512
|
-
*
|
|
1513
|
-
*
|
|
1807
|
+
* The `Err` variant of a {@link Result}: a modeled failure carrying an `error`.
|
|
1808
|
+
* This is what a successful `isErr` guard narrows to, exposing `.error`. It also
|
|
1809
|
+
* carries the shared fluent surface ({@link ResultMethods}).
|
|
1514
1810
|
*
|
|
1515
1811
|
* @remarks
|
|
1516
|
-
*
|
|
1517
|
-
*
|
|
1518
|
-
*
|
|
1519
|
-
*
|
|
1520
|
-
* `
|
|
1812
|
+
* **Note the parameter order: `ErrView<E, T>` puts the error type _first_** — the
|
|
1813
|
+
* reverse of the `<T, E>` order used by {@link OkView}, {@link DefectView}, and
|
|
1814
|
+
* {@link Result} — because `Result<T, E>` narrows to `ErrView<E, T>` (the error is
|
|
1815
|
+
* the payload the guard makes reachable). You rarely write it by hand (a failed
|
|
1816
|
+
* `isErr()` narrows to it for you); if you do, mind the flip — `ErrView<MyError,
|
|
1817
|
+
* MyValue>`, not `ErrView<MyValue, MyError>`.
|
|
1521
1818
|
*
|
|
1522
1819
|
* @example
|
|
1523
1820
|
* ```ts
|
|
1524
|
-
*
|
|
1525
|
-
*
|
|
1526
|
-
* const result = await DoAsync()
|
|
1527
|
-
* .bind("user", () => findUser(id)) // AsyncResult<User, NotFound>
|
|
1528
|
-
* .bind("plan", ({ user }) => Ok(user.plan)) // a sync Result is accepted too
|
|
1529
|
-
* .let("label", ({ user, plan }) => `${user.name} on ${plan}`);
|
|
1530
|
-
* // Result<{ user: User; plan: Plan; label: string }, NotFound>
|
|
1821
|
+
* if (r.isErr()) r.error; // r: ErrView<E, T> here — .error is an E
|
|
1531
1822
|
* ```
|
|
1532
1823
|
*
|
|
1533
|
-
* @category
|
|
1534
|
-
*/
|
|
1535
|
-
declare function DoAsync(): AsyncResult$1<{}, never>;
|
|
1536
|
-
//#endregion
|
|
1537
|
-
//#region src/interop.d.ts
|
|
1538
|
-
/**
|
|
1539
|
-
* Bridge a nullable value into a {@link Result}: absence becomes a **modeled**
|
|
1540
|
-
* `Err`. The sanctioned alternative to an `Option` type.
|
|
1541
|
-
*
|
|
1542
|
-
* @remarks
|
|
1543
|
-
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
1544
|
-
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
1545
|
-
*
|
|
1546
|
-
* @typeParam T - the (nullable) value type.
|
|
1547
|
-
* @typeParam E - the error produced when the value is absent.
|
|
1548
|
-
* @param value - the possibly-absent value.
|
|
1549
|
-
* @param onAbsent - lazily produces the error for the absent case.
|
|
1550
|
-
*
|
|
1551
|
-
* @category Interop
|
|
1552
|
-
*
|
|
1553
|
-
* @example
|
|
1554
|
-
* ```ts
|
|
1555
|
-
* import { fromNullable } from "unthrown";
|
|
1556
|
-
*
|
|
1557
|
-
* const map = new Map([["a", 1]]);
|
|
1558
|
-
* fromNullable(map.get("a"), () => "absent").getOr(0); // => 1
|
|
1559
|
-
* fromNullable(map.get("z"), () => "absent"); // => Err("absent")
|
|
1560
|
-
* fromNullable(0, () => "absent").getOr(-1); // => 0 (falsy but present)
|
|
1561
|
-
* ```
|
|
1824
|
+
* @category Types
|
|
1562
1825
|
*/
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
* @
|
|
1569
|
-
* `
|
|
1570
|
-
*
|
|
1571
|
-
* path that leaves `unknown` in `E`. A throw inside `qualify` itself is treated
|
|
1572
|
-
* as a `Defect`. `qualify` is **synchronous**: an `async` qualify is rejected at
|
|
1573
|
-
* compile time ({@link NotThenable}) — its `Promise` would land in `E` un-triaged
|
|
1574
|
-
* — and a thenable slipped past the types at runtime becomes a `Defect` (never
|
|
1575
|
-
* an `Err(Promise)`), its orphaned rejection silenced.
|
|
1576
|
-
*
|
|
1577
|
-
* `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
|
|
1578
|
-
* already returned, so its rejection could never reach `qualify`: it becomes a
|
|
1579
|
-
* `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
|
|
1580
|
-
* than left to float. Reach for {@link fromPromise} to wrap async work.
|
|
1581
|
-
*
|
|
1582
|
-
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
1583
|
-
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
1584
|
-
* `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
|
|
1585
|
-
* out-of-band and must not pollute the error channel); reach for
|
|
1586
|
-
* {@link fromSafeThrowable} when every throw is a Defect.
|
|
1587
|
-
*
|
|
1588
|
-
* @typeParam A - the wrapped function's argument tuple.
|
|
1589
|
-
* @typeParam T - the wrapped function's return type.
|
|
1590
|
-
* @typeParam R - `qualify`'s return type; the modeled error `E` is
|
|
1591
|
-
* `Exclude<R, Defect>` (its `Defect` arm, if any, is subtracted).
|
|
1592
|
-
* @param fn - the throwing function to wrap.
|
|
1593
|
-
* @param qualify - triages a thrown `cause` into a modeled `E`, or marks it
|
|
1594
|
-
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
1595
|
-
* @returns a function with the same arguments returning `Result<T, E>`.
|
|
1596
|
-
*
|
|
1597
|
-
* @category Interop
|
|
1826
|
+
interface ErrView<out E, out T = never> extends ResultMethods<T, E> {
|
|
1827
|
+
readonly tag: "Err";
|
|
1828
|
+
readonly error: E;
|
|
1829
|
+
}
|
|
1830
|
+
/**
|
|
1831
|
+
* The `Defect` variant of a {@link Result}: an unmodeled failure carrying a
|
|
1832
|
+
* `cause`. This is what a successful `isDefect` guard narrows to, exposing
|
|
1833
|
+
* `.cause`. It also carries the shared fluent surface ({@link ResultMethods}).
|
|
1598
1834
|
*
|
|
1599
1835
|
* @example
|
|
1600
1836
|
* ```ts
|
|
1601
|
-
*
|
|
1602
|
-
*
|
|
1603
|
-
* // Model the parse failure as an `Err`, everything unexpected as a `Defect`.
|
|
1604
|
-
* const parse = fromThrowable(
|
|
1605
|
-
* (text: string) => JSON.parse(text) as unknown,
|
|
1606
|
-
* (cause, defect) =>
|
|
1607
|
-
* cause instanceof SyntaxError ? ("invalid_json" as const) : defect(cause),
|
|
1608
|
-
* );
|
|
1609
|
-
*
|
|
1610
|
-
* parse('{"ok":true}').getOr(null); // => { ok: true }
|
|
1611
|
-
* parse("nope"); // => Err("invalid_json")
|
|
1837
|
+
* if (r.isDefect()) r.cause; // r: DefectView<T, E> here — .cause is `unknown`
|
|
1612
1838
|
* ```
|
|
1839
|
+
*
|
|
1840
|
+
* @category Types
|
|
1613
1841
|
*/
|
|
1614
|
-
|
|
1842
|
+
interface DefectView<out T = never, out E = never> extends ResultMethods<T, E> {
|
|
1843
|
+
readonly tag: "Defect";
|
|
1844
|
+
readonly cause: unknown;
|
|
1845
|
+
}
|
|
1615
1846
|
/**
|
|
1616
|
-
*
|
|
1617
|
-
*
|
|
1847
|
+
* A failure variant of a {@link Result}: an {@link ErrView} **or** a
|
|
1848
|
+
* {@link DefectView}. This is what a `tapFailure` callback receives — the
|
|
1849
|
+
* discriminated variant rather than a payload, because the payload union
|
|
1850
|
+
* `E | unknown` would collapse to `unknown` and lose `E`'s typing. Branch on
|
|
1851
|
+
* `tag` to narrow (`"Err"` → `.error: E`, `"Defect"` → `.cause: unknown`).
|
|
1618
1852
|
*
|
|
1619
1853
|
* @remarks
|
|
1620
|
-
*
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
* `qualify`. When some throws *are* anticipated, reach for
|
|
1624
|
-
* {@link fromThrowable} and triage them.
|
|
1625
|
-
*
|
|
1626
|
-
* `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
|
|
1627
|
-
* `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
|
|
1628
|
-
* float. Reach for {@link fromSafePromise} to wrap async work.
|
|
1629
|
-
*
|
|
1630
|
-
* @typeParam A - the wrapped function's argument tuple.
|
|
1631
|
-
* @typeParam T - the wrapped function's return type.
|
|
1632
|
-
* @param fn - the throwing function to wrap.
|
|
1633
|
-
* @returns a function with the same arguments returning `Result<T, never>`.
|
|
1634
|
-
*
|
|
1635
|
-
* @category Interop
|
|
1854
|
+
* Like {@link ErrView}, the error type comes **first** (`FailureView<E, T>`) —
|
|
1855
|
+
* the error is the payload you are usually here for, and a shared observer can
|
|
1856
|
+
* spell just `FailureView<MyError>`.
|
|
1636
1857
|
*
|
|
1637
1858
|
* @example
|
|
1638
1859
|
* ```ts
|
|
1639
|
-
*
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1642
|
-
* // every throw is a defect — no throwaway `(cause, defect) => defect(cause)`.
|
|
1643
|
-
* const decode = fromSafeThrowable((row: Row) => userSchema.parse(row));
|
|
1644
|
-
*
|
|
1645
|
-
* decode(row); // => Result<User, never> — a throw becomes a Defect
|
|
1860
|
+
* const logKo = (f: FailureView<ApiError>) =>
|
|
1861
|
+
* f.tag === "Err" ? logger.warn(f.error) : logger.error(f.cause);
|
|
1862
|
+
* result.tapFailure(logKo);
|
|
1646
1863
|
* ```
|
|
1864
|
+
*
|
|
1865
|
+
* @typeParam E - the modeled error type.
|
|
1866
|
+
* @typeParam T - the success value type (phantom here; a failure carries none).
|
|
1867
|
+
* @category Types
|
|
1647
1868
|
*/
|
|
1648
|
-
|
|
1869
|
+
type FailureView<E, T = never> = ErrView<E, T> | DefectView<T, E>;
|
|
1649
1870
|
/**
|
|
1650
|
-
*
|
|
1651
|
-
*
|
|
1871
|
+
* A success-only thenable: awaitable, but deliberately **not** a full
|
|
1872
|
+
* `PromiseLike`.
|
|
1652
1873
|
*
|
|
1653
1874
|
* @remarks
|
|
1654
|
-
*
|
|
1655
|
-
*
|
|
1656
|
-
*
|
|
1657
|
-
*
|
|
1658
|
-
*
|
|
1659
|
-
*
|
|
1660
|
-
*
|
|
1661
|
-
*
|
|
1662
|
-
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
1663
|
-
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
1664
|
-
* `qualify` that returns *only* `defect(cause)` yields `E = never`; when every
|
|
1665
|
-
* rejection is a Defect, prefer {@link fromSafePromise}.
|
|
1666
|
-
*
|
|
1667
|
-
* @typeParam T - the resolved value type.
|
|
1668
|
-
* @typeParam R - `qualify`'s return type; the modeled error `E` is
|
|
1669
|
-
* `Exclude<R, Defect>` (its `Defect` arm, if any, is subtracted).
|
|
1670
|
-
* @param promise - the promise, or a thunk returning one.
|
|
1671
|
-
* @param qualify - triages a rejection `cause` into a modeled `E`, or marks it
|
|
1672
|
-
* unmodeled by returning `defect(cause)` (the helper passed as its second arg).
|
|
1673
|
-
* @param _guard - compile-time only; never pass it. The phantom rest-tuple that
|
|
1674
|
-
* enforces "qualify is synchronous": an `async` qualify makes this demand an
|
|
1675
|
-
* impossible extra argument (whose type spells out the error), while a
|
|
1676
|
-
* synchronous one leaves it empty. Encoded here — not on `qualify`'s return
|
|
1677
|
-
* type — so `T`'s inference from `promise` is undisturbed.
|
|
1875
|
+
* An {@link AsyncResult}'s internal promise never rejects, so `await`-ing one
|
|
1876
|
+
* always yields a {@link Result} and never throws — there is no rejection
|
|
1877
|
+
* channel to model, and none is advertised. At runtime it is still a thenable
|
|
1878
|
+
* (the only way `await` can collapse it), and `Promise.all` / `Promise.resolve`
|
|
1879
|
+
* will still adopt it — harmlessly, since it settles to a `Result` and never
|
|
1880
|
+
* rejects. What the narrowing prevents is treating it as a full promise:
|
|
1881
|
+
* `.catch()` / `.finally()` do not type-check, because there is no rejection to
|
|
1882
|
+
* handle.
|
|
1678
1883
|
*
|
|
1679
|
-
* @
|
|
1884
|
+
* @typeParam T - the value `await` resolves to.
|
|
1680
1885
|
*
|
|
1681
|
-
* @
|
|
1682
|
-
|
|
1683
|
-
|
|
1886
|
+
* @category Types
|
|
1887
|
+
*/
|
|
1888
|
+
type Awaitable<out T> = {
|
|
1889
|
+
then<R = T>(onfulfilled?: ((value: T) => R | PromiseLike<R>) | null): PromiseLike<R>;
|
|
1890
|
+
};
|
|
1891
|
+
/**
|
|
1892
|
+
* The async method surface every {@link AsyncResult} carries — the combinators
|
|
1893
|
+
* (`map`, `flatMap`, `mapErrCases`, `match`, `get`, …) with their asynchronous
|
|
1894
|
+
* signatures, documented one per entry below. The async mirror of
|
|
1895
|
+
* {@link ResultMethods}: each entry links its synchronous counterpart and states
|
|
1896
|
+
* only the async delta.
|
|
1684
1897
|
*
|
|
1685
|
-
*
|
|
1686
|
-
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1898
|
+
* @remarks
|
|
1899
|
+
* Like {@link ResultMethods}, this type exists to **document** the surface — not
|
|
1900
|
+
* to be authored against; you obtain it by holding an `AsyncResult`. Its
|
|
1901
|
+
* combinator callbacks are **synchronous** (a raw `Promise` may never enter — see
|
|
1902
|
+
* the {@link AsyncResult} remarks); async work re-enters via {@link fromPromise}
|
|
1903
|
+
* and composes with `flatMap`. Systematic differences from the sync surface: the
|
|
1904
|
+
* binds return an `AsyncResult` (and additionally accept one), and the
|
|
1905
|
+
* eliminators return a `Promise`.
|
|
1689
1906
|
*
|
|
1690
|
-
*
|
|
1691
|
-
*
|
|
1692
|
-
*
|
|
1907
|
+
* @typeParam T - the success value type.
|
|
1908
|
+
* @typeParam E - the modeled error type.
|
|
1909
|
+
* @category Methods
|
|
1693
1910
|
*/
|
|
1694
|
-
|
|
1911
|
+
type AsyncResultMethods<out T, out E> = {
|
|
1912
|
+
/**
|
|
1913
|
+
* Asynchronous {@link ResultMethods.map | map}: transforms the success value
|
|
1914
|
+
* with `f`. `f` is synchronous; a throw becomes a `Defect`. An async callback
|
|
1915
|
+
* is rejected at compile time ({@link NotThenable}).
|
|
1916
|
+
*/
|
|
1917
|
+
map<U>(f: (value: T) => U & NotThenable<U>): AsyncResult<U, E>;
|
|
1918
|
+
/**
|
|
1919
|
+
* Asynchronous {@link ResultMethods.flatMap | flatMap}. Unlike the sync form,
|
|
1920
|
+
* `f` may return a `Result` **or** an `AsyncResult` (never a raw `Promise`); a
|
|
1921
|
+
* throw becomes a `Defect`.
|
|
1922
|
+
*
|
|
1923
|
+
* @remarks
|
|
1924
|
+
* The async branch of `f`'s return type is spelled `Awaitable<Result<U, E2>> &
|
|
1925
|
+
* { flatMap: unknown }` rather than `AsyncResult<U, E2>`: this is what you get
|
|
1926
|
+
* by returning an `AsyncResult` (it satisfies both), but inference runs through
|
|
1927
|
+
* the `Awaitable` then-channel so `U`/`E2` stay precise instead of collapsing
|
|
1928
|
+
* to `unknown`, while the `{ flatMap: unknown }` marker still rejects a bare
|
|
1929
|
+
* `Promise` (it has no `flatMap`). Just return a `Result` or an `AsyncResult`.
|
|
1930
|
+
*/
|
|
1931
|
+
flatMap<U, E2>(f: (value: T) => Result<U, E2> | (Awaitable<Result<U, E2>> & ReturnAnAsyncResultNotAPromise)): AsyncResult<U, E | E2>;
|
|
1932
|
+
/**
|
|
1933
|
+
* Asynchronous {@link ResultMethods.tap | tap}. `f` is synchronous; a throw
|
|
1934
|
+
* becomes a `Defect`. An async callback is rejected at compile time
|
|
1935
|
+
* ({@link NotThenable}) — and so is a returned `AsyncResult` (it is
|
|
1936
|
+
* awaitable). Beware the near-miss: _calling_ an `AsyncResult`-returning
|
|
1937
|
+
* effect inside the callback without returning it compiles and leaves the
|
|
1938
|
+
* effect floating — fire-and-forget, never awaited, its `Err`/`Defect`
|
|
1939
|
+
* unobserved. If the effect returns a `Result`/`AsyncResult`, use
|
|
1940
|
+
* {@link AsyncResultMethods.flatTap | flatTap}.
|
|
1941
|
+
*/
|
|
1942
|
+
tap<R>(f: (value: T) => R & NotThenable<R>): AsyncResult<T, E>;
|
|
1943
|
+
/**
|
|
1944
|
+
* Asynchronous {@link ResultMethods.flatTap | flatTap} — a failable tap that
|
|
1945
|
+
* keeps the original value. `f` may return a `Result` **or** an `AsyncResult`;
|
|
1946
|
+
* its `Ok` value is discarded, an `Err`/`Defect` short-circuits, and a throw
|
|
1947
|
+
* becomes a `Defect`.
|
|
1948
|
+
*/
|
|
1949
|
+
flatTap<E2>(f: (value: T) => Result<unknown, E2> | (Awaitable<Result<unknown, E2>> & ReturnAnAsyncResultNotAPromise)): AsyncResult<T, E | E2>;
|
|
1950
|
+
/**
|
|
1951
|
+
* Asynchronous {@link ResultMethods.bind | bind} (do-notation). `f` may return
|
|
1952
|
+
* a `Result` **or** an `AsyncResult`; its value is bound under `name` in the
|
|
1953
|
+
* accumulating scope.
|
|
1954
|
+
*/
|
|
1955
|
+
bind<K extends string, U, E2>(name: K, f: (scope: T) => Result<U, E2> | (Awaitable<Result<U, E2>> & ReturnAnAsyncResultNotAPromise)): AsyncResult<Bound<T, K, U>, E | E2>;
|
|
1956
|
+
/**
|
|
1957
|
+
* Asynchronous {@link ResultMethods.let | let} (do-notation). `f` returns a
|
|
1958
|
+
* plain value, bound under `name`. An async callback is rejected at compile
|
|
1959
|
+
* time ({@link NotThenable}).
|
|
1960
|
+
*/
|
|
1961
|
+
let<K extends string, U>(name: K, f: (scope: T) => U & NotThenable<U>): AsyncResult<Bound<T, K, U>, E>;
|
|
1962
|
+
/** Asynchronous {@link ResultMethods.as | as}: replaces the value with `value`. */
|
|
1963
|
+
as<U>(value: U): AsyncResult<U, E>;
|
|
1964
|
+
/** Asynchronous {@link ResultMethods.discard | discard}: drops the value, collapsing the success type to `void`. */
|
|
1965
|
+
discard(): AsyncResult<void, E>;
|
|
1966
|
+
/**
|
|
1967
|
+
* Asynchronous {@link ResultMethods.ensure | ensure}: validate the success
|
|
1968
|
+
* value — and, with a type-guard predicate (this overload), **refine** it —
|
|
1969
|
+
* failing into the modeled channel with `Err(onFail(value))`. Both callbacks
|
|
1970
|
+
* are synchronous (an async `onFail` is rejected at compile time,
|
|
1971
|
+
* {@link NotThenable}); a throw in either becomes a `Defect`.
|
|
1972
|
+
*/
|
|
1973
|
+
ensure<U extends T, E2>(predicate: (value: T) => value is U, onFail: (value: T) => E2 & NotThenable<E2>): AsyncResult<U, E | E2>;
|
|
1974
|
+
/** Boolean form of the asynchronous {@link ResultMethods.ensure | ensure} — validates without refining, keeping `T`. */
|
|
1975
|
+
ensure<E2>(predicate: (value: T) => boolean, onFail: (value: T) => E2 & NotThenable<E2>): AsyncResult<T, E | E2>;
|
|
1976
|
+
/**
|
|
1977
|
+
* Asynchronous {@link ResultMethods.mapErrCases | mapErrCases} — the same exhaustive
|
|
1978
|
+
* {@link ErrMatcher} form; the combinator calls `.exhaustive()`. Branches are
|
|
1979
|
+
* synchronous — an `async` branch is a compile error, as on the sync surface.
|
|
1980
|
+
*/
|
|
1981
|
+
mapErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M, ..._asyncBranchBanned_liftWithFromPromiseThenFlatMapErrCases: SyncBranches<MatchOut<M>, E>): AsyncResult<T, MatchErrOut<M>>;
|
|
1982
|
+
/**
|
|
1983
|
+
* Asynchronous {@link ResultMethods.flatMapErrCases | flatMapErrCases} — the same
|
|
1984
|
+
* exhaustive {@link ErrMatcher} form. Unlike the sync form, a branch may
|
|
1985
|
+
* return a `Result` **or** an `AsyncResult`.
|
|
1986
|
+
*/
|
|
1987
|
+
flatMapErrCases<M extends ExhaustiveMatch<Result<unknown, unknown> | (Awaitable<Result<unknown, unknown>> & ReturnAnAsyncResultNotAPromise) | Defect>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M): AsyncResult<T | OkOf<MatchOut<M>> | AsyncOkOf<MatchOut<M>>, ErrOf<MatchOut<M>> | AsyncErrOf<MatchOut<M>>>;
|
|
1988
|
+
/**
|
|
1989
|
+
* Asynchronous {@link ResultMethods.recoverErrCases | recoverErrCases} — the same
|
|
1990
|
+
* exhaustive {@link ErrMatcher} form. Branches are synchronous; a throw
|
|
1991
|
+
* becomes a `Defect`.
|
|
1992
|
+
*/
|
|
1993
|
+
recoverErrCases<M extends ExhaustiveMatch<unknown>>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => M, ..._asyncBranchBanned_liftWithFromPromiseThenFlatMapErrCases: SyncBranches<MatchOut<M>, T | E>): AsyncResult<T | MatchErrOut<M>, never>;
|
|
1994
|
+
/**
|
|
1995
|
+
* Asynchronous {@link ResultMethods.tapErrCases | tapErrCases}. `f` is synchronous; if it
|
|
1996
|
+
* throws — or a branch returns the injected `defect(cause)` marker, the
|
|
1997
|
+
* expression-position form of a throw — the result is a `Defect` whose cause
|
|
1998
|
+
* is an `AggregateError` of `[thrown, original failure]` — observing a failure
|
|
1999
|
+
* never destroys it. An
|
|
2000
|
+
* async branch is rejected at compile time ({@link NotThenable} on the
|
|
2001
|
+
* builder output) — other branch results are discarded, so a rejected
|
|
2002
|
+
* `Promise` would float unobserved. The
|
|
2003
|
+
* {@link AsyncResultMethods.tap | tap} fire-and-forget caveat applies here
|
|
2004
|
+
* too — a failable effect belongs in
|
|
2005
|
+
* {@link AsyncResultMethods.flatTapErrCases | flatTapErrCases}.
|
|
2006
|
+
*/
|
|
2007
|
+
tapErrCases<R>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<R & NotThenable<R>>): AsyncResult<T, E>;
|
|
2008
|
+
/**
|
|
2009
|
+
* Asynchronous {@link ResultMethods.flatTapErrCases | flatTapErrCases} — the
|
|
2010
|
+
* error-channel mirror of `flatTap`. `f` may return a `Result` **or** an
|
|
2011
|
+
* `AsyncResult`; its `Ok` value is discarded, an `Err`/`Defect` from `f`
|
|
2012
|
+
* threads through, and if `f` throws — or a branch returns the injected
|
|
2013
|
+
* `defect(cause)` marker, the expression-position form of a throw — the result
|
|
2014
|
+
* is a `Defect` whose cause is an `AggregateError` of `[thrown, original
|
|
2015
|
+
* failure]` — observing a failure never destroys it.
|
|
2016
|
+
*/
|
|
2017
|
+
flatTapErrCases<E2>(f: (matcher: ErrMatcher<E>, defect: (cause: unknown) => Defect) => ExhaustiveMatch<Result<unknown, E2> | (Awaitable<Result<unknown, E2>> & ReturnAnAsyncResultNotAPromise)>): AsyncResult<T, E | E2>;
|
|
2018
|
+
/**
|
|
2019
|
+
* Asynchronous {@link ResultMethods.recoverDefect | recoverDefect}. `f` may
|
|
2020
|
+
* return a `Result` or an `AsyncResult`.
|
|
2021
|
+
*/
|
|
2022
|
+
recoverDefect<U, E2>(f: (cause: unknown) => Result<U, E2> | AsyncResult<U, E2>): AsyncResult<T | U, E | E2>;
|
|
2023
|
+
/**
|
|
2024
|
+
* Asynchronous {@link ResultMethods.tapDefect | tapDefect}. If `f` throws, the
|
|
2025
|
+
* result is a `Defect` whose cause is an `AggregateError` of `[thrown,
|
|
2026
|
+
* original failure]` — observing a failure never destroys it. An async
|
|
2027
|
+
* callback is rejected at compile time ({@link NotThenable}).
|
|
2028
|
+
*/
|
|
2029
|
+
tapDefect<R>(f: (cause: unknown) => R & NotThenable<R>): AsyncResult<T, E>;
|
|
2030
|
+
/**
|
|
2031
|
+
* Asynchronous {@link ResultMethods.tapFailure | tapFailure} — the
|
|
2032
|
+
* cross-channel observer. `f` receives the narrowed failure variant
|
|
2033
|
+
* ({@link FailureView}); if it throws, the result is a `Defect` whose cause
|
|
2034
|
+
* is an `AggregateError` of `[thrown, original failure]` — observing a
|
|
2035
|
+
* failure never destroys it. An async callback is rejected at compile time
|
|
2036
|
+
* ({@link NotThenable}).
|
|
2037
|
+
*/
|
|
2038
|
+
tapFailure<R>(f: (failure: FailureView<E, T>) => R & NotThenable<R>): AsyncResult<T, E>;
|
|
2039
|
+
/**
|
|
2040
|
+
* Asynchronous {@link ResultMethods.match | match}. Handlers are synchronous
|
|
2041
|
+
* (the `errCases` handler returns an exhaustive {@link ErrMatcher} builder, no
|
|
2042
|
+
* `defect` helper); resolves to a `Promise` of the folded value.
|
|
2043
|
+
*/
|
|
2044
|
+
match<ROk, RDefect, M extends ExhaustiveMatch<unknown>>(cases: {
|
|
2045
|
+
ok: (value: T) => ROk;
|
|
2046
|
+
errCases: (matcher: ErrMatcher<E>) => M;
|
|
2047
|
+
defect: (cause: unknown) => RDefect;
|
|
2048
|
+
}): Promise<ROk | RDefect | MatchOut<M>>;
|
|
2049
|
+
/**
|
|
2050
|
+
* Asynchronous {@link ResultMethods.get | get}. Compiles only when the
|
|
2051
|
+
* error channel is empty (`this: AsyncResult<T, never>`); the returned promise
|
|
2052
|
+
* rejects on a `Defect` (rethrowing its cause).
|
|
2053
|
+
*/
|
|
2054
|
+
get(this: [E] extends [never] ? AsyncResult<T, never> : "unthrown: get() needs an empty error channel (E = never) — handle the Err first with recoverErrCases / match / flatMapErrCases, or use getOr / getOrElse / getOrNull / getOrUndefined"): Promise<T>;
|
|
2055
|
+
/**
|
|
2056
|
+
* Asynchronous {@link ResultMethods.getErr | getErr}. Compiles only when
|
|
2057
|
+
* the success channel is empty (`this: AsyncResult<never, E>`); the returned
|
|
2058
|
+
* promise rejects on a `Defect` (rethrowing its cause).
|
|
2059
|
+
*/
|
|
2060
|
+
getErr(this: [T] extends [never] ? AsyncResult<never, E> : "unthrown: getErr() needs an empty success channel (T = never) — narrow with isErr() first, or fold with match"): Promise<E>;
|
|
2061
|
+
/** Asynchronous {@link ResultMethods.getOr | getOr}. */
|
|
2062
|
+
getOr<U>(fallback: U): Promise<T | U>;
|
|
2063
|
+
/** Asynchronous {@link ResultMethods.getOrElse | getOrElse}. */
|
|
2064
|
+
getOrElse<U>(f: (error: E) => U): Promise<T | U>;
|
|
2065
|
+
/** Asynchronous {@link ResultMethods.getOrNull | getOrNull}. */
|
|
2066
|
+
getOrNull(): Promise<T | null>;
|
|
2067
|
+
/** Asynchronous {@link ResultMethods.getOrUndefined | getOrUndefined}. */
|
|
2068
|
+
getOrUndefined(): Promise<T | undefined>;
|
|
2069
|
+
/**
|
|
2070
|
+
* Asynchronous {@link ResultMethods.getOrThrow | getOrThrow} — the returned
|
|
2071
|
+
* promise **rejects** with the modeled error on `Err` (or the original cause
|
|
2072
|
+
* on a `Defect`), rather than throwing synchronously. Gated the same way: it
|
|
2073
|
+
* compiles only when the error channel is non-empty (`E` is not `never`).
|
|
2074
|
+
*/
|
|
2075
|
+
getOrThrow(this: [E] extends [never] ? "unthrown: getOrThrow is unnecessary here — the Err channel is empty (E = never), so there is nothing to throw. Use get() instead." : AsyncResult<T, E>): Promise<T>;
|
|
2076
|
+
};
|
|
1695
2077
|
/**
|
|
1696
|
-
*
|
|
1697
|
-
*
|
|
1698
|
-
*
|
|
1699
|
-
* @remarks
|
|
1700
|
-
* Use this only when a rejection genuinely indicates a bug rather than an
|
|
1701
|
-
* anticipated outcome — the error channel is `never`, so there is nothing to
|
|
1702
|
-
* triage. (`await`-ing still yields a `Result`; it never throws.) The
|
|
1703
|
-
* synchronous counterpart is {@link fromSafeThrowable}.
|
|
1704
|
-
*
|
|
1705
|
-
* @typeParam T - the resolved value type.
|
|
1706
|
-
* @param promise - the promise, or a thunk returning one.
|
|
2078
|
+
* Extract the success type `T` from a `Result` type — derive one type from
|
|
2079
|
+
* another instead of restating it (e.g. the payload a function returns).
|
|
1707
2080
|
*
|
|
1708
|
-
* @
|
|
2081
|
+
* @typeParam R - the `Result` type to inspect.
|
|
1709
2082
|
*
|
|
1710
2083
|
* @example
|
|
1711
2084
|
* ```ts
|
|
1712
|
-
*
|
|
1713
|
-
*
|
|
1714
|
-
*
|
|
1715
|
-
* // a rejection becomes a Defect (never a modeled Err):
|
|
1716
|
-
* await fromSafePromise(Promise.reject(new Error("boom"))); // => Defect(Error("boom"))
|
|
2085
|
+
* type R = Result<User, NotFound>;
|
|
2086
|
+
* type U = OkOf<R>; // User
|
|
2087
|
+
* type E = ErrOf<R>; // NotFound
|
|
1717
2088
|
* ```
|
|
1718
|
-
*/
|
|
1719
|
-
declare function fromSafePromise<T>(promise: Promise<T> | (() => Promise<T>)): AsyncResult$1<T, never>;
|
|
1720
|
-
/**
|
|
1721
|
-
* The settler a {@link fromExecutor} executor receives. Settles the pending
|
|
1722
|
-
* `AsyncResult` **once** — later calls are no-ops, exactly as `resolve` is on a
|
|
1723
|
-
* `Promise`.
|
|
1724
|
-
*
|
|
1725
|
-
* @typeParam T - the success type.
|
|
1726
|
-
* @typeParam E - the modeled error type.
|
|
1727
2089
|
*
|
|
1728
2090
|
* @category Types
|
|
1729
2091
|
*/
|
|
1730
|
-
type
|
|
2092
|
+
type OkOf<R> = R extends {
|
|
2093
|
+
readonly tag: "Ok";
|
|
2094
|
+
readonly value: infer T;
|
|
2095
|
+
} ? T : never;
|
|
1731
2096
|
/**
|
|
1732
|
-
*
|
|
1733
|
-
*
|
|
1734
|
-
*
|
|
1735
|
-
* @remarks
|
|
1736
|
-
* The settler takes a **`Result`**, not a value-or-reason pair: the caller names
|
|
1737
|
-
* the variant, so no `unknown` can enter `E` and there is no `qualify` to pass.
|
|
1738
|
-
* For a failure that is *not* modeled, settle the injected `defect` helper's
|
|
1739
|
-
* marker — the same injection `qualify` receives, and the only way to reach the
|
|
1740
|
-
* defect channel from inside an asynchronous callback (a `throw` there runs in
|
|
1741
|
-
* its own turn, long after the executor body returned).
|
|
1742
|
-
*
|
|
1743
|
-
* `T` and `E` cannot be inferred from the body, since `settle` is a parameter.
|
|
1744
|
-
* Supply them explicitly, or let them flow from an annotated target. Absent
|
|
1745
|
-
* either, both default to `never` (Thesis #3: no path may produce `unknown` in
|
|
1746
|
-
* `E`) — so an unannotated call is a compile error at the `settle(...)` call
|
|
1747
|
-
* site, not a silently-`unknown` channel.
|
|
1748
|
-
*
|
|
1749
|
-
* An executor that never settles yields an `AsyncResult` that never resolves —
|
|
1750
|
-
* the one hazard {@link fromPromise} does not have, and identical to
|
|
1751
|
-
* `new Promise`.
|
|
1752
|
-
*
|
|
1753
|
-
* @typeParam T - the success type.
|
|
1754
|
-
* @typeParam E - the modeled error type.
|
|
1755
|
-
* @param executor - runs immediately; receives the settler and the `defect` helper.
|
|
2097
|
+
* Extract the error type `E` from a `Result` type — the counterpart of
|
|
2098
|
+
* {@link OkOf}.
|
|
1756
2099
|
*
|
|
1757
|
-
* @
|
|
2100
|
+
* @typeParam R - the `Result` type to inspect.
|
|
1758
2101
|
*
|
|
1759
2102
|
* @example
|
|
1760
2103
|
* ```ts
|
|
1761
|
-
*
|
|
1762
|
-
*
|
|
1763
|
-
* const listen = (port: number) =>
|
|
1764
|
-
* fromExecutor<Server, PortInUse>((settle, defect) => {
|
|
1765
|
-
* server.once("error", (cause) =>
|
|
1766
|
-
* isAddrInUse(cause) ? settle(Err(new PortInUse(port))) : settle(defect(cause)),
|
|
1767
|
-
* );
|
|
1768
|
-
* server.listen(port, () => settle(Ok(server)));
|
|
1769
|
-
* });
|
|
2104
|
+
* type E = ErrOf<Result<User, NotFound>>; // NotFound
|
|
1770
2105
|
* ```
|
|
1771
|
-
*/
|
|
1772
|
-
declare function fromExecutor<T = never, E = never>(executor: (settle: Settle<T, E>, defect: (cause: unknown) => Defect) => void): AsyncResult$1<T, E>;
|
|
1773
|
-
/**
|
|
1774
|
-
* The success channel of {@link all} / {@link allAsync}: a **positional tuple**
|
|
1775
|
-
* for a fixed-length input (including the empty tuple), or a homogeneous
|
|
1776
|
-
* **array** for a dynamic one.
|
|
1777
|
-
*
|
|
1778
|
-
* @remarks
|
|
1779
|
-
* The split keys off the input's `length`: a fixed tuple has a literal length
|
|
1780
|
-
* (`number extends Rs["length"]` is false → keep the positional `Ts`), while a
|
|
1781
|
-
* general array has `length: number` (→ collapse to `Ts[number][]`). Checking
|
|
1782
|
-
* length rather than `Rs extends [unknown, ...unknown[]]` keeps `all([])` typed
|
|
1783
|
-
* as `Result<[], …>` instead of `Result<never[], …>`.
|
|
1784
|
-
*
|
|
1785
|
-
* @typeParam Rs - the tuple/array of input `Result` types.
|
|
1786
|
-
* @typeParam Ts - per-element extracted success types (`OkOf` for `all`,
|
|
1787
|
-
* `AsyncOkOf` for `allAsync`).
|
|
1788
|
-
* @internal
|
|
1789
|
-
*/
|
|
1790
|
-
type AllOk<Rs extends readonly unknown[], Ts extends readonly unknown[]> = number extends Rs["length"] ? Ts[number][] : Ts;
|
|
1791
|
-
/**
|
|
1792
|
-
* A `[key, error]` pair from a record aggregate, correlated per key.
|
|
1793
|
-
*
|
|
1794
|
-
* @remarks
|
|
1795
|
-
* `-?` because the mapped type is homomorphic: an optional input key would
|
|
1796
|
-
* otherwise carry its optionality through and put `undefined` in the entry
|
|
1797
|
-
* union, breaking the documented `([key, error]) => …` destructure. An entry
|
|
1798
|
-
* whose error channel is `never` (an infallible input — every `@unthrown/drizzle`
|
|
1799
|
-
* read) is dropped rather than emitted as an uninhabited `[K, never]` arm, which
|
|
1800
|
-
* a `switch` over the key would still have to write a dead case for.
|
|
1801
2106
|
*
|
|
1802
|
-
* @
|
|
2107
|
+
* @category Types
|
|
1803
2108
|
*/
|
|
1804
|
-
type
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
type NonEmpty<T> = readonly [T, ...T[]];
|
|
1809
|
-
/** A record of `Result`s — the input to {@link allFromDict}. */
|
|
1810
|
-
type ResultRecord = Record<string, Result$1<unknown, unknown>>;
|
|
1811
|
-
/** A record of `AsyncResult`s — the input to {@link allFromDictAsync}. */
|
|
1812
|
-
type AsyncResultRecord = Record<string, AsyncResult$1<unknown, unknown>>;
|
|
2109
|
+
type ErrOf<R> = R extends {
|
|
2110
|
+
readonly tag: "Err";
|
|
2111
|
+
readonly error: infer E;
|
|
2112
|
+
} ? E : never;
|
|
1813
2113
|
/**
|
|
1814
|
-
*
|
|
1815
|
-
*
|
|
1816
|
-
*
|
|
1817
|
-
* @remarks
|
|
1818
|
-
* Short-circuits on the **first** `Err` (later entries are not inspected for
|
|
1819
|
-
* their error); any `Defect` present **dominates**, winning even over an earlier
|
|
1820
|
-
* `Err`. A **fixed tuple** keeps its positional types — `all([Ok(1), Ok("a")])`
|
|
1821
|
-
* is `Result<[number, string], …>` — while a **dynamic array** `Result<T, E>[]`
|
|
1822
|
-
* collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
|
|
1823
|
-
* use {@link allFromDict}. To report **every** `Err` instead of only the first,
|
|
1824
|
-
* use {@link validateAll}.
|
|
2114
|
+
* Extract the success type `T` from an {@link AsyncResult} type — the async
|
|
2115
|
+
* counterpart of {@link OkOf}.
|
|
1825
2116
|
*
|
|
1826
|
-
* @
|
|
2117
|
+
* @typeParam R - the `AsyncResult` type to inspect.
|
|
1827
2118
|
*
|
|
1828
2119
|
* @example
|
|
1829
2120
|
* ```ts
|
|
1830
|
-
*
|
|
1831
|
-
*
|
|
1832
|
-
* all([Ok(1), Ok("a"), Ok(true)]).get(); // => [1, "a", true] (typed [number, string, boolean])
|
|
1833
|
-
* all([Ok(1), Err("e"), Ok(3)]); // => Err("e") (short-circuits on the first Err)
|
|
2121
|
+
* type T = AsyncOkOf<AsyncResult<User, NotFound>>; // User
|
|
1834
2122
|
* ```
|
|
1835
|
-
*/
|
|
1836
|
-
declare function all<Rs extends readonly Result$1<unknown, unknown>[]>(results: readonly [...Rs]): Result$1<AllOk<Rs, { [K in keyof Rs]: OkOf<Rs[K]>; }>, ErrOf<Rs[number]>>;
|
|
1837
|
-
/**
|
|
1838
|
-
* Collect a **record** of {@link Result}s into a single `Result` of a record of
|
|
1839
|
-
* their success values — `allFromDict({ a: Result<A, E>, b: Result<B, E> })` is
|
|
1840
|
-
* `Result<{ a: A; b: B }, E>`. The named counterpart of {@link all}, for
|
|
1841
|
-
* parallel work you'd rather not tuple.
|
|
1842
2123
|
*
|
|
1843
|
-
* @
|
|
1844
|
-
* Same folding rules as {@link all}: first `Err` short-circuits, any `Defect`
|
|
1845
|
-
* dominates. This is **not** error accumulation — for that, reach for
|
|
1846
|
-
* {@link validateAllFromDict}, which accumulates every `Err` and folds them into
|
|
1847
|
-
* one modeled error.
|
|
1848
|
-
*
|
|
1849
|
-
* @category Aggregate
|
|
1850
|
-
*
|
|
1851
|
-
* @example
|
|
1852
|
-
* ```ts
|
|
1853
|
-
* import { allFromDict, Ok, Err } from "unthrown";
|
|
1854
|
-
*
|
|
1855
|
-
* allFromDict({ id: Ok(1), name: Ok("ada") }).get(); // => { id: 1, name: "ada" }
|
|
1856
|
-
* allFromDict({ id: Ok(1), name: Err("missing") }); // => Err("missing")
|
|
1857
|
-
* ```
|
|
2124
|
+
* @category Types
|
|
1858
2125
|
*/
|
|
1859
|
-
|
|
2126
|
+
type AsyncOkOf<R> = R extends Awaitable<infer Res> ? OkOf<Res> : never;
|
|
1860
2127
|
/**
|
|
1861
|
-
*
|
|
1862
|
-
* {@link
|
|
1863
|
-
*
|
|
1864
|
-
* @remarks
|
|
1865
|
-
* The inputs are resolved **concurrently** (order preserved); the resolved
|
|
1866
|
-
* `Result`s are then folded with the same rules as {@link all} — first `Err`
|
|
1867
|
-
* short-circuits, any `Defect` dominates. As ever, the returned `AsyncResult`'s
|
|
1868
|
-
* internal promise never rejects. For a **record**, use {@link allFromDictAsync};
|
|
1869
|
-
* to report **every** `Err`, use {@link validateAllAsync}.
|
|
2128
|
+
* Extract the error type `E` from an {@link AsyncResult} type — the async
|
|
2129
|
+
* counterpart of {@link ErrOf}.
|
|
1870
2130
|
*
|
|
1871
|
-
* @
|
|
2131
|
+
* @typeParam R - the `AsyncResult` type to inspect.
|
|
1872
2132
|
*
|
|
1873
2133
|
* @example
|
|
1874
2134
|
* ```ts
|
|
1875
|
-
*
|
|
1876
|
-
*
|
|
1877
|
-
* const both = allAsync([
|
|
1878
|
-
* fromSafePromise(Promise.resolve(1)),
|
|
1879
|
-
* fromSafePromise(Promise.resolve(2)),
|
|
1880
|
-
* ]);
|
|
1881
|
-
* (await both).get(); // => [1, 2]
|
|
2135
|
+
* type E = AsyncErrOf<AsyncResult<User, NotFound>>; // NotFound
|
|
1882
2136
|
* ```
|
|
1883
|
-
*/
|
|
1884
|
-
declare function allAsync<Rs extends readonly AsyncResult$1<unknown, unknown>[]>(results: readonly [...Rs]): AsyncResult$1<AllOk<Rs, { [K in keyof Rs]: AsyncOkOf<Rs[K]>; }>, AsyncErrOf<Rs[number]>>;
|
|
1885
|
-
/**
|
|
1886
|
-
* The asynchronous counterpart of {@link allFromDict}: combine a record of
|
|
1887
|
-
* {@link AsyncResult}s into one `AsyncResult` of a record of their values.
|
|
1888
|
-
*
|
|
1889
|
-
* @remarks
|
|
1890
|
-
* Resolved concurrently (order preserved), folded with the {@link all} rules,
|
|
1891
|
-
* and the internal promise never rejects. To report **every** `Err`, use
|
|
1892
|
-
* {@link validateAllFromDictAsync}.
|
|
1893
|
-
*
|
|
1894
|
-
* @category Aggregate
|
|
1895
|
-
*
|
|
1896
|
-
* @example
|
|
1897
|
-
* ```ts
|
|
1898
|
-
* import { allFromDictAsync, fromSafePromise } from "unthrown";
|
|
1899
2137
|
*
|
|
1900
|
-
*
|
|
1901
|
-
* a: fromSafePromise(Promise.resolve(1)),
|
|
1902
|
-
* b: fromSafePromise(Promise.resolve("x")),
|
|
1903
|
-
* });
|
|
1904
|
-
* (await both).get(); // => { a: 1, b: "x" }
|
|
1905
|
-
* ```
|
|
2138
|
+
* @category Types
|
|
1906
2139
|
*/
|
|
1907
|
-
|
|
2140
|
+
type AsyncErrOf<R> = R extends Awaitable<infer Res> ? ErrOf<Res> : never;
|
|
2141
|
+
//#endregion
|
|
2142
|
+
//#region src/constructors.d.ts
|
|
1908
2143
|
/**
|
|
1909
|
-
*
|
|
1910
|
-
*
|
|
1911
|
-
*
|
|
1912
|
-
*
|
|
1913
|
-
* @remarks
|
|
1914
|
-
* Same success channel as {@link all}: a **fixed tuple** keeps its positional
|
|
1915
|
-
* types, a **dynamic array** collapses to `Result<T[], E2>`. The difference is
|
|
1916
|
-
* the error channel — instead of the first `Err` winning, every `Err` is
|
|
1917
|
-
* collected in input order and handed to `merge`, whose return becomes the
|
|
1918
|
-
* modeled error.
|
|
1919
|
-
*
|
|
1920
|
-
* `merge` receives a **non-empty** list, so it is total: it is called only when
|
|
1921
|
-
* at least one `Err` was collected. It is **not** called when every element is
|
|
1922
|
-
* `Ok`, nor when a `Defect` is present.
|
|
1923
|
-
*
|
|
1924
|
-
* Any `Defect` still **dominates** — it wins over the accumulated errors, which
|
|
1925
|
-
* are discarded and never reach `merge`. A defect means something in this batch
|
|
1926
|
-
* failed in a way nobody modeled, so the violations computed alongside it are
|
|
1927
|
-
* not trustworthy. An out-of-contract non-`Result` element becomes a
|
|
1928
|
-
* `TypeError`-caused `Defect` the same way, and a throw inside `merge` becomes
|
|
1929
|
-
* a `Defect` too.
|
|
1930
|
-
*
|
|
1931
|
-
* `merge` must be **synchronous** — an `async` one is a compile error
|
|
1932
|
-
* ({@link NotThenable}), since a `Promise` in `E` is an unqualified rejection.
|
|
2144
|
+
* Construct a successful `void` {@link Result} — `Result<void, never>` —
|
|
2145
|
+
* sparing you `Ok(undefined)` and typing the success channel `void`, not
|
|
2146
|
+
* `undefined`.
|
|
1933
2147
|
*
|
|
1934
|
-
*
|
|
1935
|
-
*
|
|
1936
|
-
*
|
|
1937
|
-
* checks you wrote yourself. For a **record** keyed by name, use
|
|
1938
|
-
* {@link validateAllFromDict}.
|
|
2148
|
+
* @example
|
|
2149
|
+
* ```ts
|
|
2150
|
+
* import { Ok } from "unthrown";
|
|
1939
2151
|
*
|
|
1940
|
-
*
|
|
1941
|
-
*
|
|
1942
|
-
* @param results - the results to collect.
|
|
1943
|
-
* @param merge - folds the collected errors into one modeled error.
|
|
2152
|
+
* Ok(); // => a void success: Result<void, never>
|
|
2153
|
+
* ```
|
|
1944
2154
|
*
|
|
1945
|
-
* @category
|
|
2155
|
+
* @category Constructors
|
|
2156
|
+
*/
|
|
2157
|
+
export declare function Ok(): Result<void, never>;
|
|
2158
|
+
/**
|
|
2159
|
+
* Construct a successful {@link Result}.
|
|
2160
|
+
*
|
|
2161
|
+
* @typeParam T - the success value type.
|
|
2162
|
+
* @param value - the success value to wrap.
|
|
1946
2163
|
*
|
|
1947
2164
|
* @example
|
|
1948
2165
|
* ```ts
|
|
1949
|
-
* import {
|
|
1950
|
-
*
|
|
1951
|
-
* // every Err is collected, not just the first
|
|
1952
|
-
* validateAll([Ok(1), Err("stock"), Err("credit")], (errors) => errors.join(" and "));
|
|
1953
|
-
* // => Err("stock and credit")
|
|
2166
|
+
* import { Ok } from "unthrown";
|
|
1954
2167
|
*
|
|
1955
|
-
*
|
|
1956
|
-
*
|
|
1957
|
-
* // => Ok([1, "a"]) typed Result<[number, string], string>
|
|
2168
|
+
* Ok(2).map((n) => n + 1); // => Ok(3)
|
|
2169
|
+
* Ok(42).get(); // => 42
|
|
1958
2170
|
* ```
|
|
2171
|
+
*
|
|
2172
|
+
* @category Constructors
|
|
1959
2173
|
*/
|
|
1960
|
-
declare function
|
|
2174
|
+
export declare function Ok<T>(value: T): Result<T, never>;
|
|
1961
2175
|
/**
|
|
1962
|
-
*
|
|
1963
|
-
* accumulating counterpart of {@link allFromDict}, and the named counterpart of
|
|
1964
|
-
* {@link validateAll}.
|
|
2176
|
+
* Construct a failed {@link Result} carrying a **modeled** error.
|
|
1965
2177
|
*
|
|
1966
|
-
* @
|
|
1967
|
-
*
|
|
1968
|
-
* per key: `{ a: Result<A, E1>; b: Result<B, E2> }` yields
|
|
1969
|
-
* `["a", E1] | ["b", E2]`, so a `switch` on the key narrows the error and an
|
|
1970
|
-
* impossible pairing does not typecheck. That is what keeps two checks sharing
|
|
1971
|
-
* one error type distinguishable. Entries come in `Object.keys` order.
|
|
2178
|
+
* @typeParam E - the modeled error type.
|
|
2179
|
+
* @param error - the domain error to wrap.
|
|
1972
2180
|
*
|
|
1973
|
-
*
|
|
1974
|
-
*
|
|
1975
|
-
*
|
|
2181
|
+
* @example
|
|
2182
|
+
* ```ts
|
|
2183
|
+
* import { Err } from "unthrown";
|
|
1976
2184
|
*
|
|
1977
|
-
*
|
|
1978
|
-
*
|
|
1979
|
-
*
|
|
1980
|
-
* @param merge - folds the collected `[key, error]` entries into one error.
|
|
2185
|
+
* Err("not_found").map((n) => n + 1); // => Err("not_found") (map skipped)
|
|
2186
|
+
* Err("not_found").getErr(); // => "not_found"
|
|
2187
|
+
* ```
|
|
1981
2188
|
*
|
|
1982
|
-
* @category
|
|
2189
|
+
* @category Constructors
|
|
2190
|
+
*/
|
|
2191
|
+
export declare function Err<E>(error: E): Result<never, E>;
|
|
2192
|
+
/**
|
|
2193
|
+
* Construct a successful `void` {@link AsyncResult} — `AsyncResult<void, never>`
|
|
2194
|
+
* — the pre-lifted form of the no-arg {@link Ok}, sparing you
|
|
2195
|
+
* `Ok(undefined).toAsync()`.
|
|
1983
2196
|
*
|
|
1984
2197
|
* @example
|
|
1985
2198
|
* ```ts
|
|
1986
|
-
* import {
|
|
2199
|
+
* import { OkAsync } from "unthrown";
|
|
1987
2200
|
*
|
|
1988
|
-
*
|
|
1989
|
-
* { vatRate: Err("out of range"), currency: Ok("EUR"), dueDate: Err("past") },
|
|
1990
|
-
* (entries) => entries.map(([key, error]) => `${key}: ${error}`).join("; "),
|
|
1991
|
-
* );
|
|
1992
|
-
* // => Err("vatRate: out of range; dueDate: past")
|
|
2201
|
+
* OkAsync(); // => a void success: AsyncResult<void, never>
|
|
1993
2202
|
* ```
|
|
2203
|
+
*
|
|
2204
|
+
* @category Constructors
|
|
1994
2205
|
*/
|
|
1995
|
-
declare function
|
|
2206
|
+
export declare function OkAsync(): AsyncResult<void, never>;
|
|
1996
2207
|
/**
|
|
1997
|
-
*
|
|
1998
|
-
* {@link
|
|
2208
|
+
* Construct a successful {@link AsyncResult} from a pure value — the pre-lifted
|
|
2209
|
+
* form of {@link Ok}, sparing you `Ok(value).toAsync()`.
|
|
1999
2210
|
*
|
|
2000
2211
|
* @remarks
|
|
2001
|
-
*
|
|
2002
|
-
*
|
|
2003
|
-
*
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2006
|
-
* here too — this is exactly where its rejection would land unqualified in `E`.
|
|
2007
|
-
* For a **record**, use {@link validateAllFromDictAsync}.
|
|
2008
|
-
*
|
|
2009
|
-
* @typeParam Rs - the tuple/array of input `AsyncResult` types.
|
|
2010
|
-
* @typeParam E2 - the merged error type.
|
|
2011
|
-
* @param results - the async results to collect.
|
|
2012
|
-
* @param merge - folds the collected errors into one modeled error.
|
|
2212
|
+
* Reach for this on the synchronous/early branch of an `AsyncResult`-returning
|
|
2213
|
+
* function, so both branches share one return type without a trailing
|
|
2214
|
+
* `.toAsync()`. Named with the `Async` suffix the async free functions carry
|
|
2215
|
+
* (`allAsync`, `allFromDictAsync`); the {@link AsyncResult} companion aliases it
|
|
2216
|
+
* as `AsyncResult.Ok` (the namespace already says "async", so the suffix drops).
|
|
2013
2217
|
*
|
|
2014
|
-
* @
|
|
2218
|
+
* @typeParam T - the success value type.
|
|
2219
|
+
* @param value - the success value to wrap.
|
|
2015
2220
|
*
|
|
2016
2221
|
* @example
|
|
2017
2222
|
* ```ts
|
|
2018
|
-
* import {
|
|
2223
|
+
* import { OkAsync, type AsyncResult } from "unthrown";
|
|
2019
2224
|
*
|
|
2020
|
-
*
|
|
2021
|
-
*
|
|
2022
|
-
*
|
|
2023
|
-
*
|
|
2024
|
-
* // (await checked) => Err("stock and credit")
|
|
2225
|
+
* function loadItems(ids: string[]): AsyncResult<Item[], never> {
|
|
2226
|
+
* if (ids.length === 0) return OkAsync([]); // no more Ok([]).toAsync()
|
|
2227
|
+
* return itemRepository.load(ids);
|
|
2228
|
+
* }
|
|
2025
2229
|
* ```
|
|
2230
|
+
*
|
|
2231
|
+
* @category Constructors
|
|
2026
2232
|
*/
|
|
2027
|
-
declare function
|
|
2233
|
+
export declare function OkAsync<T>(value: T): AsyncResult<T, never>;
|
|
2028
2234
|
/**
|
|
2029
|
-
*
|
|
2030
|
-
* of {@link
|
|
2235
|
+
* Construct a failed {@link AsyncResult} carrying a **modeled** error — the
|
|
2236
|
+
* pre-lifted form of {@link Err}, sparing you `Err(error).toAsync()`.
|
|
2031
2237
|
*
|
|
2032
2238
|
* @remarks
|
|
2033
|
-
* The {@link
|
|
2034
|
-
*
|
|
2035
|
-
*
|
|
2036
|
-
* @typeParam R - the record of input `AsyncResult` types.
|
|
2037
|
-
* @typeParam E2 - the merged error type.
|
|
2038
|
-
* @param results - the async results to collect, keyed by name.
|
|
2039
|
-
* @param merge - folds the collected `[key, error]` entries into one error.
|
|
2239
|
+
* The error-channel mirror of {@link OkAsync}; see it for the naming and the
|
|
2240
|
+
* `AsyncResult.Err` companion alias.
|
|
2040
2241
|
*
|
|
2041
|
-
* @
|
|
2242
|
+
* @typeParam E - the modeled error type.
|
|
2243
|
+
* @param error - the domain error to wrap.
|
|
2042
2244
|
*
|
|
2043
2245
|
* @example
|
|
2044
2246
|
* ```ts
|
|
2045
|
-
* import {
|
|
2247
|
+
* import { ErrAsync } from "unthrown";
|
|
2046
2248
|
*
|
|
2047
|
-
*
|
|
2048
|
-
* { stock: ErrAsync("none left"), credit: OkAsync(500) },
|
|
2049
|
-
* (entries) => entries.map(([key, error]) => `${key}: ${error}`).join("; "),
|
|
2050
|
-
* );
|
|
2051
|
-
* // (await checked) => Err("stock: none left")
|
|
2249
|
+
* ErrAsync("not_found"); // AsyncResult<never, string>
|
|
2052
2250
|
* ```
|
|
2251
|
+
*
|
|
2252
|
+
* @category Constructors
|
|
2053
2253
|
*/
|
|
2054
|
-
declare function
|
|
2055
|
-
//#endregion
|
|
2056
|
-
//#region src/facade.d.ts
|
|
2254
|
+
export declare function ErrAsync<E>(error: E): AsyncResult<never, E>;
|
|
2057
2255
|
/**
|
|
2058
|
-
*
|
|
2059
|
-
* single, discoverable namespace: {@link Result.Ok}, {@link Result.Err},
|
|
2060
|
-
* {@link Result.Do}, {@link Result.fromNullable}, {@link Result.fromThrowable},
|
|
2061
|
-
* {@link Result.fromSafeThrowable}, {@link Result.all},
|
|
2062
|
-
* {@link Result.allFromDict}, {@link Result.validateAll},
|
|
2063
|
-
* {@link Result.validateAllFromDict}, {@link Result.isOk}, {@link Result.isErr},
|
|
2064
|
-
* {@link Result.isDefect}, {@link Result.isResult}.
|
|
2065
|
-
*
|
|
2066
|
-
* @remarks
|
|
2067
|
-
* Purely additive sugar — each member **is** the corresponding free function.
|
|
2068
|
-
* The free functions remain the primary, tree-shakeable API; importing only
|
|
2069
|
-
* `{ Ok }` never pulls this object in. The value `Result` and the type
|
|
2070
|
-
* {@link Result} share one name (the companion-object pattern).
|
|
2071
|
-
*
|
|
2072
|
-
* The **async** entry points live on the sibling {@link AsyncResult} companion
|
|
2073
|
-
* (`AsyncResult.fromPromise`, `AsyncResult.all`, …), grouped by what they
|
|
2074
|
-
* return — a static lives in exactly one namespace.
|
|
2256
|
+
* Type guard: narrow a {@link Result} to its `Ok` variant, exposing `.value`.
|
|
2075
2257
|
*
|
|
2076
|
-
* @
|
|
2258
|
+
* @returns `true` when `r` is `Ok`.
|
|
2077
2259
|
*
|
|
2078
2260
|
* @example
|
|
2079
2261
|
* ```ts
|
|
2080
|
-
* import { Result } from "unthrown";
|
|
2081
|
-
*
|
|
2262
|
+
* import { isOk, Ok, Err, type Result } from "unthrown";
|
|
2263
|
+
*
|
|
2264
|
+
* isOk(Ok(1)); // => true
|
|
2265
|
+
* isOk(Err("boom")); // => false
|
|
2266
|
+
*
|
|
2267
|
+
* declare const r: Result<number, string>;
|
|
2268
|
+
* if (isOk(r)) r.value; // number, narrowed
|
|
2082
2269
|
* ```
|
|
2270
|
+
*
|
|
2271
|
+
* @category Guards
|
|
2083
2272
|
*/
|
|
2084
|
-
declare
|
|
2085
|
-
readonly Ok: typeof Ok;
|
|
2086
|
-
readonly Err: typeof Err;
|
|
2087
|
-
readonly Do: typeof Do;
|
|
2088
|
-
readonly fromNullable: typeof fromNullable;
|
|
2089
|
-
readonly fromThrowable: typeof fromThrowable;
|
|
2090
|
-
readonly fromSafeThrowable: typeof fromSafeThrowable;
|
|
2091
|
-
readonly all: typeof all;
|
|
2092
|
-
readonly allFromDict: typeof allFromDict;
|
|
2093
|
-
readonly validateAll: typeof validateAll;
|
|
2094
|
-
readonly validateAllFromDict: typeof validateAllFromDict;
|
|
2095
|
-
readonly isOk: typeof isOk;
|
|
2096
|
-
readonly isErr: typeof isErr;
|
|
2097
|
-
readonly isDefect: typeof isDefect;
|
|
2098
|
-
readonly isResult: typeof isResult;
|
|
2099
|
-
};
|
|
2273
|
+
export declare function isOk<T, E>(r: Result<T, E>): r is OkView<T, E>;
|
|
2100
2274
|
/**
|
|
2101
|
-
*
|
|
2102
|
-
* {@link Result | companion object} above (the value and type are one name); this
|
|
2103
|
-
* is the type half.
|
|
2275
|
+
* Type guard: narrow a {@link Result} to its `Err` variant, exposing `.error`.
|
|
2104
2276
|
*
|
|
2105
|
-
* @
|
|
2106
|
-
* A `Result` is a discriminated union, so TypeDoc can't list its methods on this
|
|
2107
|
-
* alias. Its fluent combinators (`map`, `flatMap`, `match`, `get`, …) are
|
|
2108
|
-
* documented one per entry on {@link ResultMethods} — the shared method surface
|
|
2109
|
-
* every variant carries. For "which one do I reach for?", see the
|
|
2110
|
-
* [Choosing a combinator](/reference/combinators) guide.
|
|
2277
|
+
* @returns `true` when `r` is `Err`.
|
|
2111
2278
|
*
|
|
2112
|
-
* @
|
|
2279
|
+
* @example
|
|
2280
|
+
* ```ts
|
|
2281
|
+
* import { isErr, Ok, Err, type Result } from "unthrown";
|
|
2282
|
+
*
|
|
2283
|
+
* isErr(Err("boom")); // => true
|
|
2284
|
+
* isErr(Ok(1)); // => false
|
|
2285
|
+
*
|
|
2286
|
+
* declare const r: Result<number, string>;
|
|
2287
|
+
* if (isErr(r)) r.error; // string, narrowed
|
|
2288
|
+
* ```
|
|
2289
|
+
*
|
|
2290
|
+
* @category Guards
|
|
2113
2291
|
*/
|
|
2114
|
-
|
|
2292
|
+
export declare function isErr<T, E>(r: Result<T, E>): r is ErrView<E, T>;
|
|
2115
2293
|
/**
|
|
2116
|
-
*
|
|
2117
|
-
* the matching namespace: {@link AsyncResult.Ok}, {@link AsyncResult.Err},
|
|
2118
|
-
* {@link AsyncResult.Do}, {@link AsyncResult.fromExecutor},
|
|
2119
|
-
* {@link AsyncResult.fromPromise}, {@link AsyncResult.fromSafePromise},
|
|
2120
|
-
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict},
|
|
2121
|
-
* {@link AsyncResult.validateAll}, {@link AsyncResult.validateAllFromDict}.
|
|
2294
|
+
* Type guard: narrow a {@link Result} to its `Defect` variant, exposing `.cause`.
|
|
2122
2295
|
*
|
|
2123
2296
|
* @remarks
|
|
2124
|
-
*
|
|
2125
|
-
*
|
|
2126
|
-
* `fromPromise`/`fromSafePromise`, and the async aggregates sit here rather
|
|
2127
|
-
* than on {@link Result}; the namespace
|
|
2128
|
-
* already conveys "async", so the members drop the `Async` suffix their free
|
|
2129
|
-
* functions carry (`AsyncResult.Ok` is `OkAsync`; `AsyncResult.Err` is
|
|
2130
|
-
* `ErrAsync`; `AsyncResult.Do` is `DoAsync`; `AsyncResult.all` is `allAsync`;
|
|
2131
|
-
* `AsyncResult.allFromDict` is
|
|
2132
|
-
* `allFromDictAsync`; `AsyncResult.validateAll` is `validateAllAsync`). Like
|
|
2133
|
-
* {@link Result}, the free functions remain the
|
|
2134
|
-
* primary, tree-shakeable API; the value `AsyncResult` and the type
|
|
2135
|
-
* {@link AsyncResult} share one name.
|
|
2297
|
+
* A `Defect` has no public constructor — it only arises at a boundary (e.g. a
|
|
2298
|
+
* callback throwing inside a combinator). This guard is how you detect one.
|
|
2136
2299
|
*
|
|
2137
|
-
* @
|
|
2300
|
+
* @returns `true` when `r` is a `Defect`.
|
|
2138
2301
|
*
|
|
2139
2302
|
* @example
|
|
2140
2303
|
* ```ts
|
|
2141
|
-
* import {
|
|
2142
|
-
* const user = await AsyncResult.fromPromise(
|
|
2143
|
-
* fetchUser(id),
|
|
2144
|
-
* (c, defect) => defect(c),
|
|
2145
|
-
* );
|
|
2146
|
-
* user.get(); // => the fetched user (on success)
|
|
2147
|
-
* ```
|
|
2148
|
-
*/
|
|
2149
|
-
declare const AsyncResult: {
|
|
2150
|
-
readonly Ok: typeof OkAsync;
|
|
2151
|
-
readonly Err: typeof ErrAsync;
|
|
2152
|
-
readonly Do: typeof DoAsync;
|
|
2153
|
-
readonly fromExecutor: typeof fromExecutor;
|
|
2154
|
-
readonly fromPromise: typeof fromPromise;
|
|
2155
|
-
readonly fromSafePromise: typeof fromSafePromise;
|
|
2156
|
-
readonly all: typeof allAsync;
|
|
2157
|
-
readonly allFromDict: typeof allFromDictAsync;
|
|
2158
|
-
readonly validateAll: typeof validateAllAsync;
|
|
2159
|
-
readonly validateAllFromDict: typeof validateAllFromDictAsync;
|
|
2160
|
-
};
|
|
2161
|
-
/**
|
|
2162
|
-
* `AsyncResult<T, E>` — the async counterpart of {@link Result}. Shares its name
|
|
2163
|
-
* with the {@link AsyncResult | companion object} above (value and type are one
|
|
2164
|
-
* name); this is the type half.
|
|
2304
|
+
* import { isDefect, Ok } from "unthrown";
|
|
2165
2305
|
*
|
|
2166
|
-
*
|
|
2167
|
-
*
|
|
2168
|
-
*
|
|
2169
|
-
*
|
|
2170
|
-
*
|
|
2306
|
+
* // A throw inside a combinator is captured as a Defect:
|
|
2307
|
+
* const r = Ok(1).map(() => {
|
|
2308
|
+
* throw new Error("boom");
|
|
2309
|
+
* });
|
|
2310
|
+
* isDefect(r); // => true
|
|
2311
|
+
* isDefect(Ok(1)); // => false
|
|
2171
2312
|
*
|
|
2172
|
-
*
|
|
2313
|
+
* if (isDefect(r)) r.cause; // unknown, narrowed
|
|
2314
|
+
* ```
|
|
2315
|
+
*
|
|
2316
|
+
* @category Guards
|
|
2173
2317
|
*/
|
|
2174
|
-
|
|
2318
|
+
export declare function isDefect<T, E>(r: Result<T, E>): r is DefectView<T, E>;
|
|
2175
2319
|
//#endregion
|
|
2176
2320
|
//#region src/tagged.d.ts
|
|
2177
2321
|
type Props = Record<string, unknown>;
|
|
@@ -2277,8 +2421,8 @@ type TaggedErrorConstructor<Tag extends string> = {
|
|
|
2277
2421
|
* new HttpError({ status: 500 }).status; // => 500
|
|
2278
2422
|
* ```
|
|
2279
2423
|
*/
|
|
2280
|
-
declare function TaggedError<Tag extends string>(tag: Tag, options?: {
|
|
2424
|
+
export declare function TaggedError<Tag extends string>(tag: Tag, options?: {
|
|
2281
2425
|
readonly name?: string;
|
|
2282
2426
|
}): TaggedErrorConstructor<Tag>;
|
|
2283
2427
|
//#endregion
|
|
2284
|
-
export {
|
|
2428
|
+
export type { AsyncErrOf, AsyncOkOf, AsyncResultMethods, Awaitable, DefectView, ErrMatcher, ErrOf, ErrView, FailureView, Matcher, NotThenable, OkOf, OkView, PatternMatcher, ResultMethods, Settle, TaggedErrorConstructor, TaggedErrorInstance, UniversalPattern };
|