unthrown 0.2.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/index.cjs +282 -76
- package/dist/index.d.cts +201 -61
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +201 -61
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +277 -74
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +270 -138
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -11,10 +11,10 @@ pnpm add unthrown
|
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
import {
|
|
14
|
+
import { Ok, Err, fromPromise, Defect, type Result } from "unthrown";
|
|
15
15
|
|
|
16
16
|
const user = fromPromise(fetchUser(id), (cause) =>
|
|
17
|
-
cause instanceof NotFoundError ? new NotFound() :
|
|
17
|
+
cause instanceof NotFoundError ? new NotFound() : Defect(cause),
|
|
18
18
|
);
|
|
19
19
|
|
|
20
20
|
const status = await user.match({
|
package/dist/index.cjs
CHANGED
|
@@ -33,7 +33,7 @@ var UnwrapError = class extends Error {
|
|
|
33
33
|
*/
|
|
34
34
|
var Res = class {
|
|
35
35
|
map(f) {
|
|
36
|
-
if (this.tag !== "Ok") return this;
|
|
36
|
+
if (this.tag !== "Ok") return passThrough(this);
|
|
37
37
|
try {
|
|
38
38
|
return okRes(f(this.value));
|
|
39
39
|
} catch (cause) {
|
|
@@ -41,7 +41,7 @@ var Res = class {
|
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
flatMap(f) {
|
|
44
|
-
if (this.tag !== "Ok") return this;
|
|
44
|
+
if (this.tag !== "Ok") return passThrough(this);
|
|
45
45
|
try {
|
|
46
46
|
return f(this.value);
|
|
47
47
|
} catch (cause) {
|
|
@@ -57,12 +57,45 @@ var Res = class {
|
|
|
57
57
|
return defectRes(cause);
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
-
|
|
60
|
+
flatTap(f) {
|
|
61
61
|
if (this.tag !== "Ok") return this;
|
|
62
|
+
try {
|
|
63
|
+
const r = f(this.value);
|
|
64
|
+
return r.tag === "Ok" ? this : passThrough(r);
|
|
65
|
+
} catch (cause) {
|
|
66
|
+
return defectRes(cause);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
bind(name, f) {
|
|
70
|
+
if (this.tag !== "Ok") return passThrough(this);
|
|
71
|
+
try {
|
|
72
|
+
const r = f(this.value);
|
|
73
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
74
|
+
return okRes({
|
|
75
|
+
...scopeOf(this.value),
|
|
76
|
+
[name]: r.value
|
|
77
|
+
});
|
|
78
|
+
} catch (cause) {
|
|
79
|
+
return defectRes(cause);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
let(name, f) {
|
|
83
|
+
if (this.tag !== "Ok") return passThrough(this);
|
|
84
|
+
try {
|
|
85
|
+
return okRes({
|
|
86
|
+
...scopeOf(this.value),
|
|
87
|
+
[name]: f(this.value)
|
|
88
|
+
});
|
|
89
|
+
} catch (cause) {
|
|
90
|
+
return defectRes(cause);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
as(value) {
|
|
94
|
+
if (this.tag !== "Ok") return passThrough(this);
|
|
62
95
|
return okRes(value);
|
|
63
96
|
}
|
|
64
97
|
mapErr(f) {
|
|
65
|
-
if (this.tag !== "Err") return this;
|
|
98
|
+
if (this.tag !== "Err") return passThrough(this);
|
|
66
99
|
try {
|
|
67
100
|
return errRes(f(this.error));
|
|
68
101
|
} catch (cause) {
|
|
@@ -70,7 +103,7 @@ var Res = class {
|
|
|
70
103
|
}
|
|
71
104
|
}
|
|
72
105
|
orElse(f) {
|
|
73
|
-
if (this.tag !== "Err") return this;
|
|
106
|
+
if (this.tag !== "Err") return passThrough(this);
|
|
74
107
|
try {
|
|
75
108
|
return f(this.error);
|
|
76
109
|
} catch (cause) {
|
|
@@ -78,7 +111,7 @@ var Res = class {
|
|
|
78
111
|
}
|
|
79
112
|
}
|
|
80
113
|
recover(f) {
|
|
81
|
-
if (this.tag !== "Err") return this;
|
|
114
|
+
if (this.tag !== "Err") return passThrough(this);
|
|
82
115
|
try {
|
|
83
116
|
return okRes(f(this.error));
|
|
84
117
|
} catch (cause) {
|
|
@@ -199,6 +232,41 @@ function defectRes(cause) {
|
|
|
199
232
|
});
|
|
200
233
|
}
|
|
201
234
|
/**
|
|
235
|
+
* Reuse a non-matching variant (an `Err` or `Defect`) as a differently-typed
|
|
236
|
+
* `Result`, with no runtime work. Sound because the passed-through variant
|
|
237
|
+
* carries no value of the changed success type, so retyping it is a no-op — only
|
|
238
|
+
* the phantom type parameter moves. This is the single sanctioned home for that
|
|
239
|
+
* assertion (the same one boxed applies inline at every pass-through); every
|
|
240
|
+
* combinator's short-circuit branch funnels through here instead of casting.
|
|
241
|
+
*
|
|
242
|
+
* @internal
|
|
243
|
+
*/
|
|
244
|
+
function passThrough(self) {
|
|
245
|
+
return self;
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Validate that a `bind`/`let` scope is a real (non-null) object before merging a
|
|
249
|
+
* key into it.
|
|
250
|
+
*
|
|
251
|
+
* @remarks
|
|
252
|
+
* Do-notation accumulates an **object** scope: a chain starts at `Do()` (an
|
|
253
|
+
* empty object) and every `bind`/`let` returns an object, so in typed code the
|
|
254
|
+
* scope is always an object. The method lives on the general `Result` surface,
|
|
255
|
+
* though, so a primitive `Ok` (e.g. `Ok(5).bind(...)`, or a chain whose value was
|
|
256
|
+
* `map`-ped away from its scope) could reach it. Rather than let `{ ...5 }`
|
|
257
|
+
* silently collapse to `{}` and drop the prior scope, we throw here — the
|
|
258
|
+
* surrounding `try` turns it into a {@link Defect}, surfacing the misuse as the
|
|
259
|
+
* bug it is (a defect is a bug, not an absent value). A `this: object` constraint
|
|
260
|
+
* was rejected: TypeScript does not hard-enforce a constraint inferred solely
|
|
261
|
+
* from `this`, and it breaks `AsyncRes implements AsyncResult`.
|
|
262
|
+
*
|
|
263
|
+
* @internal
|
|
264
|
+
*/
|
|
265
|
+
function scopeOf(value) {
|
|
266
|
+
if (typeof value !== "object" || value === null) throw new TypeError("bind/let requires an object scope — start a do-chain with Do()");
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
202
270
|
* The sole runtime implementation of {@link AsyncResult}: wraps a
|
|
203
271
|
* `Promise<Result>` constructed never to reject. Operates on the public `Result`
|
|
204
272
|
* union (via `tag`), never on `Res` internals. Never re-exported from `index.ts`.
|
|
@@ -215,7 +283,7 @@ var AsyncRes = class AsyncRes {
|
|
|
215
283
|
}
|
|
216
284
|
map(f) {
|
|
217
285
|
return new AsyncRes(this.promise.then((r) => {
|
|
218
|
-
if (r.tag !== "Ok") return r;
|
|
286
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
219
287
|
try {
|
|
220
288
|
return okRes(f(r.value));
|
|
221
289
|
} catch (cause) {
|
|
@@ -225,7 +293,7 @@ var AsyncRes = class AsyncRes {
|
|
|
225
293
|
}
|
|
226
294
|
flatMap(f) {
|
|
227
295
|
return new AsyncRes(this.promise.then(async (r) => {
|
|
228
|
-
if (r.tag !== "Ok") return r;
|
|
296
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
229
297
|
try {
|
|
230
298
|
return await f(r.value);
|
|
231
299
|
} catch (cause) {
|
|
@@ -244,12 +312,51 @@ var AsyncRes = class AsyncRes {
|
|
|
244
312
|
}
|
|
245
313
|
}));
|
|
246
314
|
}
|
|
315
|
+
flatTap(f) {
|
|
316
|
+
return new AsyncRes(this.promise.then(async (r) => {
|
|
317
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
318
|
+
try {
|
|
319
|
+
const inner = await f(r.value);
|
|
320
|
+
return inner.tag === "Ok" ? r : passThrough(inner);
|
|
321
|
+
} catch (cause) {
|
|
322
|
+
return defectRes(cause);
|
|
323
|
+
}
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
bind(name, f) {
|
|
327
|
+
return new AsyncRes(this.promise.then(async (r) => {
|
|
328
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
329
|
+
try {
|
|
330
|
+
const inner = await f(r.value);
|
|
331
|
+
if (inner.tag !== "Ok") return passThrough(inner);
|
|
332
|
+
return okRes({
|
|
333
|
+
...scopeOf(r.value),
|
|
334
|
+
[name]: inner.value
|
|
335
|
+
});
|
|
336
|
+
} catch (cause) {
|
|
337
|
+
return defectRes(cause);
|
|
338
|
+
}
|
|
339
|
+
}));
|
|
340
|
+
}
|
|
341
|
+
let(name, f) {
|
|
342
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
343
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
344
|
+
try {
|
|
345
|
+
return okRes({
|
|
346
|
+
...scopeOf(r.value),
|
|
347
|
+
[name]: f(r.value)
|
|
348
|
+
});
|
|
349
|
+
} catch (cause) {
|
|
350
|
+
return defectRes(cause);
|
|
351
|
+
}
|
|
352
|
+
}));
|
|
353
|
+
}
|
|
247
354
|
as(value) {
|
|
248
|
-
return new AsyncRes(this.promise.then((r) => r.tag === "Ok" ? okRes(value) : r));
|
|
355
|
+
return new AsyncRes(this.promise.then((r) => r.tag === "Ok" ? okRes(value) : passThrough(r)));
|
|
249
356
|
}
|
|
250
357
|
mapErr(f) {
|
|
251
358
|
return new AsyncRes(this.promise.then((r) => {
|
|
252
|
-
if (r.tag !== "Err") return r;
|
|
359
|
+
if (r.tag !== "Err") return passThrough(r);
|
|
253
360
|
try {
|
|
254
361
|
return errRes(f(r.error));
|
|
255
362
|
} catch (cause) {
|
|
@@ -259,7 +366,7 @@ var AsyncRes = class AsyncRes {
|
|
|
259
366
|
}
|
|
260
367
|
orElse(f) {
|
|
261
368
|
return new AsyncRes(this.promise.then(async (r) => {
|
|
262
|
-
if (r.tag !== "Err") return r;
|
|
369
|
+
if (r.tag !== "Err") return passThrough(r);
|
|
263
370
|
try {
|
|
264
371
|
return await f(r.error);
|
|
265
372
|
} catch (cause) {
|
|
@@ -269,7 +376,7 @@ var AsyncRes = class AsyncRes {
|
|
|
269
376
|
}
|
|
270
377
|
recover(f) {
|
|
271
378
|
return new AsyncRes(this.promise.then((r) => {
|
|
272
|
-
if (r.tag !== "Err") return r;
|
|
379
|
+
if (r.tag !== "Err") return passThrough(r);
|
|
273
380
|
try {
|
|
274
381
|
return okRes(f(r.error));
|
|
275
382
|
} catch (cause) {
|
|
@@ -345,11 +452,11 @@ var AsyncRes = class AsyncRes {
|
|
|
345
452
|
*
|
|
346
453
|
* @example
|
|
347
454
|
* ```ts
|
|
348
|
-
* import {
|
|
349
|
-
*
|
|
455
|
+
* import { Ok } from "unthrown";
|
|
456
|
+
* Ok(42).unwrap(); // 42
|
|
350
457
|
* ```
|
|
351
458
|
*/
|
|
352
|
-
function
|
|
459
|
+
function Ok(value) {
|
|
353
460
|
return okRes(value);
|
|
354
461
|
}
|
|
355
462
|
/**
|
|
@@ -360,11 +467,11 @@ function ok(value) {
|
|
|
360
467
|
*
|
|
361
468
|
* @example
|
|
362
469
|
* ```ts
|
|
363
|
-
* import {
|
|
364
|
-
*
|
|
470
|
+
* import { Err } from "unthrown";
|
|
471
|
+
* Err("not_found").unwrapErr(); // "not_found"
|
|
365
472
|
* ```
|
|
366
473
|
*/
|
|
367
|
-
function
|
|
474
|
+
function Err(error) {
|
|
368
475
|
return errRes(error);
|
|
369
476
|
}
|
|
370
477
|
/**
|
|
@@ -400,24 +507,24 @@ function isDefect(r) {
|
|
|
400
507
|
}
|
|
401
508
|
//#endregion
|
|
402
509
|
//#region src/defect.ts
|
|
403
|
-
const DEFECT = Symbol("unthrown/
|
|
510
|
+
const DEFECT = Symbol("unthrown/Defect");
|
|
404
511
|
/**
|
|
405
512
|
* Wrap a cause as a {@link Defect} — the value you return from a `qualify`
|
|
406
513
|
* function when a failure is **not** a modeled domain error.
|
|
407
514
|
*
|
|
408
515
|
* @param cause - the original thrown/rejected value.
|
|
409
|
-
* @returns an opaque
|
|
516
|
+
* @returns an opaque Defect marker carrying `cause`.
|
|
410
517
|
*
|
|
411
518
|
* @example
|
|
412
519
|
* ```ts
|
|
413
|
-
* import { fromPromise,
|
|
520
|
+
* import { fromPromise, Defect } from "unthrown";
|
|
414
521
|
*
|
|
415
522
|
* const user = fromPromise(fetchUser(id), (cause) =>
|
|
416
|
-
* cause instanceof NotFoundError ? cause :
|
|
523
|
+
* cause instanceof NotFoundError ? cause : Defect(cause),
|
|
417
524
|
* );
|
|
418
525
|
* ```
|
|
419
526
|
*/
|
|
420
|
-
function
|
|
527
|
+
function Defect(cause) {
|
|
421
528
|
return {
|
|
422
529
|
[DEFECT]: true,
|
|
423
530
|
cause
|
|
@@ -434,13 +541,40 @@ function isDefectMarker(x) {
|
|
|
434
541
|
return typeof x === "object" && x !== null && x[DEFECT] === true;
|
|
435
542
|
}
|
|
436
543
|
//#endregion
|
|
544
|
+
//#region src/do.ts
|
|
545
|
+
/**
|
|
546
|
+
* Start a do-notation chain with an empty object scope, grown step by step with
|
|
547
|
+
* `bind` (for `Result`-returning steps) and `let` (for pure values).
|
|
548
|
+
*
|
|
549
|
+
* @remarks
|
|
550
|
+
* Capitalised because `do` is a reserved word. Each step receives the scope
|
|
551
|
+
* accumulated so far; the error types union across `bind`s, and a throw in any
|
|
552
|
+
* step becomes a `Defect`. To go asynchronous, lift the chain with `toAsync()`
|
|
553
|
+
* (then a `bind` may return an `AsyncResult`).
|
|
554
|
+
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* import { Do, Ok } from "unthrown";
|
|
558
|
+
*
|
|
559
|
+
* const result = Do()
|
|
560
|
+
* .bind("user", () => findUser(id)) // Result<User, NotFound>
|
|
561
|
+
* .bind("org", ({ user }) => findOrg(user.orgId)) // Result<Org, NotFound>
|
|
562
|
+
* .let("label", ({ user, org }) => `${user.name} @ ${org.name}`)
|
|
563
|
+
* .map(({ user, org, label }) => render(user, org, label));
|
|
564
|
+
* // Result<View, NotFound>
|
|
565
|
+
* ```
|
|
566
|
+
*/
|
|
567
|
+
function Do() {
|
|
568
|
+
return Ok({});
|
|
569
|
+
}
|
|
570
|
+
//#endregion
|
|
437
571
|
//#region src/interop.ts
|
|
438
572
|
/**
|
|
439
573
|
* Bridge a nullable value into a {@link Result}: absence becomes a **modeled**
|
|
440
574
|
* `Err`. The sanctioned alternative to an `Option` type.
|
|
441
575
|
*
|
|
442
576
|
* @remarks
|
|
443
|
-
* `null` and `undefined` map to `
|
|
577
|
+
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
444
578
|
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
445
579
|
*
|
|
446
580
|
* @typeParam T - the (nullable) value type.
|
|
@@ -455,7 +589,7 @@ function isDefectMarker(x) {
|
|
|
455
589
|
* ```
|
|
456
590
|
*/
|
|
457
591
|
function fromNullable(value, onAbsent) {
|
|
458
|
-
return value === null || value === void 0 ?
|
|
592
|
+
return value === null || value === void 0 ? Err(onAbsent()) : Ok(value);
|
|
459
593
|
}
|
|
460
594
|
/**
|
|
461
595
|
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
|
@@ -463,14 +597,14 @@ function fromNullable(value, onAbsent) {
|
|
|
463
597
|
*
|
|
464
598
|
* @remarks
|
|
465
599
|
* `qualify` **must** triage every thrown cause into a modeled error `E` or a
|
|
466
|
-
* {@link Defect} (via {@link
|
|
600
|
+
* {@link Defect} (via {@link Defect}) — there is no path that leaves `unknown`
|
|
467
601
|
* in `E`. A throw inside `qualify` itself is treated as a `Defect`.
|
|
468
602
|
*
|
|
469
603
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
470
604
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
471
|
-
* `qualify` that returns *only* `
|
|
605
|
+
* `qualify` that returns *only* `Defect(cause)` yields `E = never` (a Defect is
|
|
472
606
|
* out-of-band and must not pollute the error channel); reach for
|
|
473
|
-
* {@link fromSafePromise} when every failure is a
|
|
607
|
+
* {@link fromSafePromise} when every failure is a Defect.
|
|
474
608
|
*
|
|
475
609
|
* @typeParam A - the wrapped function's argument tuple.
|
|
476
610
|
* @typeParam T - the wrapped function's return type.
|
|
@@ -482,8 +616,8 @@ function fromNullable(value, onAbsent) {
|
|
|
482
616
|
*
|
|
483
617
|
* @example
|
|
484
618
|
* ```ts
|
|
485
|
-
* import { fromThrowable,
|
|
486
|
-
* const parse = fromThrowable(JSON.parse, (cause) =>
|
|
619
|
+
* import { fromThrowable, Defect } from "unthrown";
|
|
620
|
+
* const parse = fromThrowable(JSON.parse, (cause) => Defect(cause));
|
|
487
621
|
* parse("{}").unwrap();
|
|
488
622
|
* ```
|
|
489
623
|
*/
|
|
@@ -491,7 +625,7 @@ function fromThrowable(fn, qualify) {
|
|
|
491
625
|
const triage = qualify;
|
|
492
626
|
return (...args) => {
|
|
493
627
|
try {
|
|
494
|
-
return
|
|
628
|
+
return Ok(fn(...args));
|
|
495
629
|
} catch (cause) {
|
|
496
630
|
return qualifyToResult(cause, triage);
|
|
497
631
|
}
|
|
@@ -509,8 +643,8 @@ function fromThrowable(fn, qualify) {
|
|
|
509
643
|
*
|
|
510
644
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
511
645
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
512
|
-
* `qualify` that returns *only* `
|
|
513
|
-
* rejection is a
|
|
646
|
+
* `qualify` that returns *only* `Defect(cause)` yields `E = never`; when every
|
|
647
|
+
* rejection is a Defect, prefer {@link fromSafePromise}.
|
|
514
648
|
*
|
|
515
649
|
* @typeParam T - the resolved value type.
|
|
516
650
|
* @typeParam R - `qualify`'s return type; the modeled error `E` is
|
|
@@ -520,9 +654,9 @@ function fromThrowable(fn, qualify) {
|
|
|
520
654
|
*
|
|
521
655
|
* @example
|
|
522
656
|
* ```ts
|
|
523
|
-
* import { fromPromise,
|
|
657
|
+
* import { fromPromise, Defect } from "unthrown";
|
|
524
658
|
* const user = await fromPromise(fetchUser(id), (cause) =>
|
|
525
|
-
* cause instanceof NotFoundError ? ("not_found" as const) :
|
|
659
|
+
* cause instanceof NotFoundError ? ("not_found" as const) : Defect(cause),
|
|
526
660
|
* );
|
|
527
661
|
* ```
|
|
528
662
|
*/
|
|
@@ -554,92 +688,161 @@ function qualifyToResult(cause, qualify) {
|
|
|
554
688
|
}
|
|
555
689
|
}
|
|
556
690
|
/**
|
|
557
|
-
*
|
|
691
|
+
* Fold an array of settled `Result`s: first `Err` wins, any `Defect` dominates,
|
|
692
|
+
* else `Ok` of the values array.
|
|
693
|
+
*
|
|
694
|
+
* @internal
|
|
695
|
+
*/
|
|
696
|
+
function foldArray(results) {
|
|
697
|
+
let firstErr;
|
|
698
|
+
let firstDefect;
|
|
699
|
+
const values = [];
|
|
700
|
+
for (const r of results) if (r.tag === "Defect") firstDefect ??= r;
|
|
701
|
+
else if (r.tag === "Err") firstErr ??= r;
|
|
702
|
+
else values.push(r.value);
|
|
703
|
+
return firstDefect ?? firstErr ?? Ok(values);
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Fold a record of settled `Result`s with the same rules, else `Ok` of the
|
|
707
|
+
* record of values. Keys are written with `Object.defineProperty` so a
|
|
708
|
+
* caller-supplied `"__proto__"` key cannot pollute the prototype.
|
|
709
|
+
*
|
|
710
|
+
* @internal
|
|
711
|
+
*/
|
|
712
|
+
function foldRecord(results) {
|
|
713
|
+
let firstErr;
|
|
714
|
+
let firstDefect;
|
|
715
|
+
const values = {};
|
|
716
|
+
for (const [key, r] of Object.entries(results)) if (r.tag === "Defect") firstDefect ??= r;
|
|
717
|
+
else if (r.tag === "Err") firstErr ??= r;
|
|
718
|
+
else Object.defineProperty(values, key, {
|
|
719
|
+
value: r.value,
|
|
720
|
+
enumerable: true,
|
|
721
|
+
writable: true,
|
|
722
|
+
configurable: true
|
|
723
|
+
});
|
|
724
|
+
return firstDefect ?? firstErr ?? Ok(values);
|
|
725
|
+
}
|
|
726
|
+
/**
|
|
727
|
+
* Collect a tuple/array of {@link Result}s into a single `Result` of all their
|
|
728
|
+
* success values.
|
|
558
729
|
*
|
|
559
730
|
* @remarks
|
|
560
731
|
* Short-circuits on the **first** `Err` (later entries are not inspected for
|
|
561
732
|
* their error); any `Defect` present **dominates**, winning even over an earlier
|
|
562
|
-
* `Err`. A **fixed tuple** keeps its positional types — `all([
|
|
733
|
+
* `Err`. A **fixed tuple** keeps its positional types — `all([Ok(1), Ok("a")])`
|
|
563
734
|
* is `Result<[number, string], …>` — while a **dynamic array** `Result<T, E>[]`
|
|
564
|
-
* collapses to `Result<T[], E>` with no cast.
|
|
565
|
-
*
|
|
566
|
-
* @typeParam Rs - the tuple/array of input `Result` types.
|
|
567
|
-
* @param results - the results to combine.
|
|
735
|
+
* collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
|
|
736
|
+
* use {@link allFromDict}.
|
|
568
737
|
*
|
|
569
738
|
* @example
|
|
570
739
|
* ```ts
|
|
571
|
-
* import { all,
|
|
572
|
-
* all([
|
|
573
|
-
* all([
|
|
740
|
+
* import { all, Ok } from "unthrown";
|
|
741
|
+
* all([Ok(1), Ok("a"), Ok(true)]).unwrap(); // [1, "a", true] (typed [number, string, boolean])
|
|
742
|
+
* all([Ok(1), Ok(2)] as Result<number, never>[]).unwrap(); // number[]
|
|
574
743
|
* ```
|
|
575
744
|
*/
|
|
576
745
|
function all(results) {
|
|
577
|
-
|
|
578
|
-
let firstErr;
|
|
579
|
-
let firstDefect;
|
|
580
|
-
for (const r of results) if (r.tag === "Defect") firstDefect ??= r;
|
|
581
|
-
else if (r.tag === "Err") firstErr ??= r;
|
|
582
|
-
else values.push(r.value);
|
|
583
|
-
if (firstDefect) return firstDefect;
|
|
584
|
-
if (firstErr) return firstErr;
|
|
585
|
-
return ok(values);
|
|
746
|
+
return foldArray(results);
|
|
586
747
|
}
|
|
587
748
|
/**
|
|
588
|
-
*
|
|
589
|
-
*
|
|
749
|
+
* Collect a **record** of {@link Result}s into a single `Result` of a record of
|
|
750
|
+
* their success values — `allFromDict({ a: Result<A, E>, b: Result<B, E> })` is
|
|
751
|
+
* `Result<{ a: A; b: B }, E>`. The named counterpart of {@link all}, for
|
|
752
|
+
* parallel work you'd rather not tuple.
|
|
590
753
|
*
|
|
591
754
|
* @remarks
|
|
592
|
-
*
|
|
593
|
-
*
|
|
594
|
-
*
|
|
595
|
-
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
755
|
+
* Same folding rules as {@link all}: first `Err` short-circuits, any `Defect`
|
|
756
|
+
* dominates. This is **not** error accumulation.
|
|
757
|
+
*
|
|
758
|
+
* @example
|
|
759
|
+
* ```ts
|
|
760
|
+
* import { allFromDict, Ok } from "unthrown";
|
|
761
|
+
* allFromDict({ id: Ok(1), name: Ok("ada") }).unwrap(); // { id: 1, name: "ada" }
|
|
762
|
+
* ```
|
|
763
|
+
*/
|
|
764
|
+
function allFromDict(results) {
|
|
765
|
+
return foldRecord(results);
|
|
766
|
+
}
|
|
767
|
+
/**
|
|
768
|
+
* The asynchronous counterpart of {@link all}: combine a tuple/array of
|
|
769
|
+
* {@link AsyncResult}s into one `AsyncResult` of all their success values.
|
|
598
770
|
*
|
|
599
|
-
* @
|
|
600
|
-
*
|
|
771
|
+
* @remarks
|
|
772
|
+
* The inputs are resolved **concurrently** (order preserved); the resolved
|
|
773
|
+
* `Result`s are then folded with the same rules as {@link all} — first `Err`
|
|
774
|
+
* short-circuits, any `Defect` dominates. As ever, the returned `AsyncResult`'s
|
|
775
|
+
* internal promise never rejects. For a **record**, use {@link allFromDictAsync}.
|
|
601
776
|
*
|
|
602
777
|
* @example
|
|
603
778
|
* ```ts
|
|
604
779
|
* import { allAsync, fromSafePromise } from "unthrown";
|
|
605
|
-
*
|
|
780
|
+
* await allAsync([fromSafePromise(a()), fromSafePromise(b())]);
|
|
606
781
|
* ```
|
|
607
782
|
*/
|
|
608
783
|
function allAsync(results) {
|
|
609
|
-
return new AsyncRes(Promise.all(results).then((resolved) =>
|
|
784
|
+
return new AsyncRes(Promise.all(results).then((resolved) => foldArray(resolved)));
|
|
785
|
+
}
|
|
786
|
+
/**
|
|
787
|
+
* The asynchronous counterpart of {@link allFromDict}: combine a record of
|
|
788
|
+
* {@link AsyncResult}s into one `AsyncResult` of a record of their values.
|
|
789
|
+
*
|
|
790
|
+
* @remarks
|
|
791
|
+
* Resolved concurrently (order preserved), folded with the {@link all} rules,
|
|
792
|
+
* and the internal promise never rejects.
|
|
793
|
+
*
|
|
794
|
+
* @example
|
|
795
|
+
* ```ts
|
|
796
|
+
* import { allFromDictAsync, fromSafePromise } from "unthrown";
|
|
797
|
+
* await allFromDictAsync({ a: fromSafePromise(a()), b: fromSafePromise(b()) });
|
|
798
|
+
* ```
|
|
799
|
+
*/
|
|
800
|
+
function allFromDictAsync(results) {
|
|
801
|
+
const entries = Object.entries(results);
|
|
802
|
+
return new AsyncRes(Promise.all(entries.map(([, ar]) => ar)).then((resolved) => {
|
|
803
|
+
const byKey = Object.create(null);
|
|
804
|
+
entries.forEach(([key], i) => {
|
|
805
|
+
byKey[key] = resolved[i];
|
|
806
|
+
});
|
|
807
|
+
return foldRecord(byKey);
|
|
808
|
+
}));
|
|
610
809
|
}
|
|
611
810
|
//#endregion
|
|
612
811
|
//#region src/facade.ts
|
|
613
812
|
/**
|
|
614
813
|
* Companion object grouping the standalone entry points under a single,
|
|
615
|
-
* discoverable namespace: {@link Result.
|
|
616
|
-
* {@link Result.
|
|
814
|
+
* discoverable namespace: {@link Result.Ok}, {@link Result.Err},
|
|
815
|
+
* {@link Result.Defect}, {@link Result.fromNullable}, {@link Result.fromThrowable},
|
|
617
816
|
* {@link Result.fromPromise}, {@link Result.fromSafePromise}, {@link Result.all},
|
|
618
|
-
* {@link Result.allAsync}, {@link Result.
|
|
817
|
+
* {@link Result.allAsync}, {@link Result.allFromDict},
|
|
818
|
+
* {@link Result.allFromDictAsync}, {@link Result.isOk}, {@link Result.isErr},
|
|
619
819
|
* {@link Result.isDefect}.
|
|
620
820
|
*
|
|
621
821
|
* @remarks
|
|
622
822
|
* Purely additive sugar — each member **is** the corresponding free function.
|
|
623
823
|
* The free functions remain the primary, tree-shakeable API; importing only
|
|
624
|
-
* `{
|
|
824
|
+
* `{ Ok }` never pulls this object in. The value `Result` and the type
|
|
625
825
|
* {@link Result} share one name (the companion-object pattern).
|
|
626
826
|
*
|
|
627
827
|
* @example
|
|
628
828
|
* ```ts
|
|
629
829
|
* import { Result } from "unthrown";
|
|
630
|
-
* Result.
|
|
830
|
+
* Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // 2
|
|
631
831
|
* ```
|
|
632
832
|
*/
|
|
633
833
|
const Result = {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
834
|
+
Ok,
|
|
835
|
+
Err,
|
|
836
|
+
Defect,
|
|
837
|
+
Do,
|
|
637
838
|
fromNullable,
|
|
638
839
|
fromThrowable,
|
|
639
840
|
fromPromise,
|
|
640
841
|
fromSafePromise,
|
|
641
842
|
all,
|
|
642
843
|
allAsync,
|
|
844
|
+
allFromDict,
|
|
845
|
+
allFromDictAsync,
|
|
643
846
|
isOk,
|
|
644
847
|
isErr,
|
|
645
848
|
isDefect
|
|
@@ -712,13 +915,17 @@ function matchTags(result, handlers) {
|
|
|
712
915
|
});
|
|
713
916
|
}
|
|
714
917
|
//#endregion
|
|
918
|
+
exports.Defect = Defect;
|
|
919
|
+
exports.Do = Do;
|
|
920
|
+
exports.Err = Err;
|
|
921
|
+
exports.Ok = Ok;
|
|
715
922
|
exports.Result = Result;
|
|
716
923
|
exports.TaggedError = TaggedError;
|
|
717
924
|
exports.UnwrapError = UnwrapError;
|
|
718
925
|
exports.all = all;
|
|
719
926
|
exports.allAsync = allAsync;
|
|
720
|
-
exports.
|
|
721
|
-
exports.
|
|
927
|
+
exports.allFromDict = allFromDict;
|
|
928
|
+
exports.allFromDictAsync = allFromDictAsync;
|
|
722
929
|
exports.fromNullable = fromNullable;
|
|
723
930
|
exports.fromPromise = fromPromise;
|
|
724
931
|
exports.fromSafePromise = fromSafePromise;
|
|
@@ -727,4 +934,3 @@ exports.isDefect = isDefect;
|
|
|
727
934
|
exports.isErr = isErr;
|
|
728
935
|
exports.isOk = isOk;
|
|
729
936
|
exports.matchTags = matchTags;
|
|
730
|
-
exports.ok = ok;
|