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