unthrown 0.3.0 → 1.1.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 +211 -58
- package/dist/index.d.cts +168 -47
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +168 -47
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +207 -56
- package/dist/index.mjs.map +1 -1
- package/docs/index.md +197 -121
- 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) {
|
|
@@ -61,17 +61,41 @@ var Res = class {
|
|
|
61
61
|
if (this.tag !== "Ok") return this;
|
|
62
62
|
try {
|
|
63
63
|
const r = f(this.value);
|
|
64
|
-
return r.tag === "Ok" ? this : r;
|
|
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
|
+
});
|
|
65
89
|
} catch (cause) {
|
|
66
90
|
return defectRes(cause);
|
|
67
91
|
}
|
|
68
92
|
}
|
|
69
93
|
as(value) {
|
|
70
|
-
if (this.tag !== "Ok") return this;
|
|
94
|
+
if (this.tag !== "Ok") return passThrough(this);
|
|
71
95
|
return okRes(value);
|
|
72
96
|
}
|
|
73
97
|
mapErr(f) {
|
|
74
|
-
if (this.tag !== "Err") return this;
|
|
98
|
+
if (this.tag !== "Err") return passThrough(this);
|
|
75
99
|
try {
|
|
76
100
|
return errRes(f(this.error));
|
|
77
101
|
} catch (cause) {
|
|
@@ -79,7 +103,7 @@ var Res = class {
|
|
|
79
103
|
}
|
|
80
104
|
}
|
|
81
105
|
orElse(f) {
|
|
82
|
-
if (this.tag !== "Err") return this;
|
|
106
|
+
if (this.tag !== "Err") return passThrough(this);
|
|
83
107
|
try {
|
|
84
108
|
return f(this.error);
|
|
85
109
|
} catch (cause) {
|
|
@@ -87,7 +111,7 @@ var Res = class {
|
|
|
87
111
|
}
|
|
88
112
|
}
|
|
89
113
|
recover(f) {
|
|
90
|
-
if (this.tag !== "Err") return this;
|
|
114
|
+
if (this.tag !== "Err") return passThrough(this);
|
|
91
115
|
try {
|
|
92
116
|
return okRes(f(this.error));
|
|
93
117
|
} catch (cause) {
|
|
@@ -103,6 +127,15 @@ var Res = class {
|
|
|
103
127
|
return defectRes(cause);
|
|
104
128
|
}
|
|
105
129
|
}
|
|
130
|
+
flatTapErr(f) {
|
|
131
|
+
if (this.tag !== "Err") return this;
|
|
132
|
+
try {
|
|
133
|
+
const r = f(this.error);
|
|
134
|
+
return r.tag === "Ok" ? this : passThrough(r);
|
|
135
|
+
} catch (cause) {
|
|
136
|
+
return defectRes(cause);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
106
139
|
recoverDefect(f) {
|
|
107
140
|
if (this.tag !== "Defect") return this;
|
|
108
141
|
try {
|
|
@@ -208,6 +241,56 @@ function defectRes(cause) {
|
|
|
208
241
|
});
|
|
209
242
|
}
|
|
210
243
|
/**
|
|
244
|
+
* Type guard: is `x` a {@link Result} (any of `Ok` / `Err` / `Defect`)?
|
|
245
|
+
*
|
|
246
|
+
* @remarks
|
|
247
|
+
* Unlike {@link isOk} / {@link isErr} / {@link isDefect}, which narrow a value
|
|
248
|
+
* already known to be a `Result`, this narrows from `unknown` — useful at an
|
|
249
|
+
* untyped boundary. It checks the value carries the `Result` prototype, so a
|
|
250
|
+
* look-alike plain object (`{ tag: "Ok" }`) is **not** matched. An `AsyncResult`
|
|
251
|
+
* is not a `Result` and returns `false`.
|
|
252
|
+
*
|
|
253
|
+
* @returns `true` when `x` is a `Result` produced by this library.
|
|
254
|
+
*/
|
|
255
|
+
function isResult(x) {
|
|
256
|
+
return x instanceof Res;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Reuse a non-matching variant (an `Err` or `Defect`) as a differently-typed
|
|
260
|
+
* `Result`, with no runtime work. Sound because the passed-through variant
|
|
261
|
+
* carries no value of the changed success type, so retyping it is a no-op — only
|
|
262
|
+
* the phantom type parameter moves. This is the single sanctioned home for that
|
|
263
|
+
* assertion (the same one boxed applies inline at every pass-through); every
|
|
264
|
+
* combinator's short-circuit branch funnels through here instead of casting.
|
|
265
|
+
*
|
|
266
|
+
* @internal
|
|
267
|
+
*/
|
|
268
|
+
function passThrough(self) {
|
|
269
|
+
return self;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Validate that a `bind`/`let` scope is a real (non-null) object before merging a
|
|
273
|
+
* key into it.
|
|
274
|
+
*
|
|
275
|
+
* @remarks
|
|
276
|
+
* Do-notation accumulates an **object** scope: a chain starts at `Do()` (an
|
|
277
|
+
* empty object) and every `bind`/`let` returns an object, so in typed code the
|
|
278
|
+
* scope is always an object. The method lives on the general `Result` surface,
|
|
279
|
+
* though, so a primitive `Ok` (e.g. `Ok(5).bind(...)`, or a chain whose value was
|
|
280
|
+
* `map`-ped away from its scope) could reach it. Rather than let `{ ...5 }`
|
|
281
|
+
* silently collapse to `{}` and drop the prior scope, we throw here — the
|
|
282
|
+
* surrounding `try` turns it into a {@link Defect}, surfacing the misuse as the
|
|
283
|
+
* bug it is (a defect is a bug, not an absent value). A `this: object` constraint
|
|
284
|
+
* was rejected: TypeScript does not hard-enforce a constraint inferred solely
|
|
285
|
+
* from `this`, and it breaks `AsyncRes implements AsyncResult`.
|
|
286
|
+
*
|
|
287
|
+
* @internal
|
|
288
|
+
*/
|
|
289
|
+
function scopeOf(value) {
|
|
290
|
+
if (typeof value !== "object" || value === null) throw new TypeError("bind/let requires an object scope — start a do-chain with Do()");
|
|
291
|
+
return value;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
211
294
|
* The sole runtime implementation of {@link AsyncResult}: wraps a
|
|
212
295
|
* `Promise<Result>` constructed never to reject. Operates on the public `Result`
|
|
213
296
|
* union (via `tag`), never on `Res` internals. Never re-exported from `index.ts`.
|
|
@@ -224,7 +307,7 @@ var AsyncRes = class AsyncRes {
|
|
|
224
307
|
}
|
|
225
308
|
map(f) {
|
|
226
309
|
return new AsyncRes(this.promise.then((r) => {
|
|
227
|
-
if (r.tag !== "Ok") return r;
|
|
310
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
228
311
|
try {
|
|
229
312
|
return okRes(f(r.value));
|
|
230
313
|
} catch (cause) {
|
|
@@ -234,7 +317,7 @@ var AsyncRes = class AsyncRes {
|
|
|
234
317
|
}
|
|
235
318
|
flatMap(f) {
|
|
236
319
|
return new AsyncRes(this.promise.then(async (r) => {
|
|
237
|
-
if (r.tag !== "Ok") return r;
|
|
320
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
238
321
|
try {
|
|
239
322
|
return await f(r.value);
|
|
240
323
|
} catch (cause) {
|
|
@@ -255,21 +338,49 @@ var AsyncRes = class AsyncRes {
|
|
|
255
338
|
}
|
|
256
339
|
flatTap(f) {
|
|
257
340
|
return new AsyncRes(this.promise.then(async (r) => {
|
|
258
|
-
if (r.tag !== "Ok") return r;
|
|
341
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
342
|
+
try {
|
|
343
|
+
const inner = await f(r.value);
|
|
344
|
+
return inner.tag === "Ok" ? r : passThrough(inner);
|
|
345
|
+
} catch (cause) {
|
|
346
|
+
return defectRes(cause);
|
|
347
|
+
}
|
|
348
|
+
}));
|
|
349
|
+
}
|
|
350
|
+
bind(name, f) {
|
|
351
|
+
return new AsyncRes(this.promise.then(async (r) => {
|
|
352
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
259
353
|
try {
|
|
260
354
|
const inner = await f(r.value);
|
|
261
|
-
|
|
355
|
+
if (inner.tag !== "Ok") return passThrough(inner);
|
|
356
|
+
return okRes({
|
|
357
|
+
...scopeOf(r.value),
|
|
358
|
+
[name]: inner.value
|
|
359
|
+
});
|
|
360
|
+
} catch (cause) {
|
|
361
|
+
return defectRes(cause);
|
|
362
|
+
}
|
|
363
|
+
}));
|
|
364
|
+
}
|
|
365
|
+
let(name, f) {
|
|
366
|
+
return new AsyncRes(this.promise.then((r) => {
|
|
367
|
+
if (r.tag !== "Ok") return passThrough(r);
|
|
368
|
+
try {
|
|
369
|
+
return okRes({
|
|
370
|
+
...scopeOf(r.value),
|
|
371
|
+
[name]: f(r.value)
|
|
372
|
+
});
|
|
262
373
|
} catch (cause) {
|
|
263
374
|
return defectRes(cause);
|
|
264
375
|
}
|
|
265
376
|
}));
|
|
266
377
|
}
|
|
267
378
|
as(value) {
|
|
268
|
-
return new AsyncRes(this.promise.then((r) => r.tag === "Ok" ? okRes(value) : r));
|
|
379
|
+
return new AsyncRes(this.promise.then((r) => r.tag === "Ok" ? okRes(value) : passThrough(r)));
|
|
269
380
|
}
|
|
270
381
|
mapErr(f) {
|
|
271
382
|
return new AsyncRes(this.promise.then((r) => {
|
|
272
|
-
if (r.tag !== "Err") return r;
|
|
383
|
+
if (r.tag !== "Err") return passThrough(r);
|
|
273
384
|
try {
|
|
274
385
|
return errRes(f(r.error));
|
|
275
386
|
} catch (cause) {
|
|
@@ -279,7 +390,7 @@ var AsyncRes = class AsyncRes {
|
|
|
279
390
|
}
|
|
280
391
|
orElse(f) {
|
|
281
392
|
return new AsyncRes(this.promise.then(async (r) => {
|
|
282
|
-
if (r.tag !== "Err") return r;
|
|
393
|
+
if (r.tag !== "Err") return passThrough(r);
|
|
283
394
|
try {
|
|
284
395
|
return await f(r.error);
|
|
285
396
|
} catch (cause) {
|
|
@@ -289,7 +400,7 @@ var AsyncRes = class AsyncRes {
|
|
|
289
400
|
}
|
|
290
401
|
recover(f) {
|
|
291
402
|
return new AsyncRes(this.promise.then((r) => {
|
|
292
|
-
if (r.tag !== "Err") return r;
|
|
403
|
+
if (r.tag !== "Err") return passThrough(r);
|
|
293
404
|
try {
|
|
294
405
|
return okRes(f(r.error));
|
|
295
406
|
} catch (cause) {
|
|
@@ -308,6 +419,17 @@ var AsyncRes = class AsyncRes {
|
|
|
308
419
|
}
|
|
309
420
|
}));
|
|
310
421
|
}
|
|
422
|
+
flatTapErr(f) {
|
|
423
|
+
return new AsyncRes(this.promise.then(async (r) => {
|
|
424
|
+
if (r.tag !== "Err") return passThrough(r);
|
|
425
|
+
try {
|
|
426
|
+
const inner = await f(r.error);
|
|
427
|
+
return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
|
|
428
|
+
} catch (cause) {
|
|
429
|
+
return defectRes(cause);
|
|
430
|
+
}
|
|
431
|
+
}));
|
|
432
|
+
}
|
|
311
433
|
recoverDefect(f) {
|
|
312
434
|
return new AsyncRes(this.promise.then(async (r) => {
|
|
313
435
|
if (r.tag !== "Defect") return r;
|
|
@@ -365,11 +487,11 @@ var AsyncRes = class AsyncRes {
|
|
|
365
487
|
*
|
|
366
488
|
* @example
|
|
367
489
|
* ```ts
|
|
368
|
-
* import {
|
|
369
|
-
*
|
|
490
|
+
* import { Ok } from "unthrown";
|
|
491
|
+
* Ok(42).unwrap(); // 42
|
|
370
492
|
* ```
|
|
371
493
|
*/
|
|
372
|
-
function
|
|
494
|
+
function Ok(value) {
|
|
373
495
|
return okRes(value);
|
|
374
496
|
}
|
|
375
497
|
/**
|
|
@@ -380,11 +502,11 @@ function ok(value) {
|
|
|
380
502
|
*
|
|
381
503
|
* @example
|
|
382
504
|
* ```ts
|
|
383
|
-
* import {
|
|
384
|
-
*
|
|
505
|
+
* import { Err } from "unthrown";
|
|
506
|
+
* Err("not_found").unwrapErr(); // "not_found"
|
|
385
507
|
* ```
|
|
386
508
|
*/
|
|
387
|
-
function
|
|
509
|
+
function Err(error) {
|
|
388
510
|
return errRes(error);
|
|
389
511
|
}
|
|
390
512
|
/**
|
|
@@ -420,24 +542,24 @@ function isDefect(r) {
|
|
|
420
542
|
}
|
|
421
543
|
//#endregion
|
|
422
544
|
//#region src/defect.ts
|
|
423
|
-
const DEFECT = Symbol("unthrown/
|
|
545
|
+
const DEFECT = Symbol("unthrown/Defect");
|
|
424
546
|
/**
|
|
425
547
|
* Wrap a cause as a {@link Defect} — the value you return from a `qualify`
|
|
426
548
|
* function when a failure is **not** a modeled domain error.
|
|
427
549
|
*
|
|
428
550
|
* @param cause - the original thrown/rejected value.
|
|
429
|
-
* @returns an opaque
|
|
551
|
+
* @returns an opaque Defect marker carrying `cause`.
|
|
430
552
|
*
|
|
431
553
|
* @example
|
|
432
554
|
* ```ts
|
|
433
|
-
* import { fromPromise,
|
|
555
|
+
* import { fromPromise, Defect } from "unthrown";
|
|
434
556
|
*
|
|
435
557
|
* const user = fromPromise(fetchUser(id), (cause) =>
|
|
436
|
-
* cause instanceof NotFoundError ? cause :
|
|
558
|
+
* cause instanceof NotFoundError ? cause : Defect(cause),
|
|
437
559
|
* );
|
|
438
560
|
* ```
|
|
439
561
|
*/
|
|
440
|
-
function
|
|
562
|
+
function Defect(cause) {
|
|
441
563
|
return {
|
|
442
564
|
[DEFECT]: true,
|
|
443
565
|
cause
|
|
@@ -454,13 +576,40 @@ function isDefectMarker(x) {
|
|
|
454
576
|
return typeof x === "object" && x !== null && x[DEFECT] === true;
|
|
455
577
|
}
|
|
456
578
|
//#endregion
|
|
579
|
+
//#region src/do.ts
|
|
580
|
+
/**
|
|
581
|
+
* Start a do-notation chain with an empty object scope, grown step by step with
|
|
582
|
+
* `bind` (for `Result`-returning steps) and `let` (for pure values).
|
|
583
|
+
*
|
|
584
|
+
* @remarks
|
|
585
|
+
* Capitalised because `do` is a reserved word. Each step receives the scope
|
|
586
|
+
* accumulated so far; the error types union across `bind`s, and a throw in any
|
|
587
|
+
* step becomes a `Defect`. To go asynchronous, lift the chain with `toAsync()`
|
|
588
|
+
* (then a `bind` may return an `AsyncResult`).
|
|
589
|
+
*
|
|
590
|
+
* @example
|
|
591
|
+
* ```ts
|
|
592
|
+
* import { Do, Ok } from "unthrown";
|
|
593
|
+
*
|
|
594
|
+
* const result = Do()
|
|
595
|
+
* .bind("user", () => findUser(id)) // Result<User, NotFound>
|
|
596
|
+
* .bind("org", ({ user }) => findOrg(user.orgId)) // Result<Org, NotFound>
|
|
597
|
+
* .let("label", ({ user, org }) => `${user.name} @ ${org.name}`)
|
|
598
|
+
* .map(({ user, org, label }) => render(user, org, label));
|
|
599
|
+
* // Result<View, NotFound>
|
|
600
|
+
* ```
|
|
601
|
+
*/
|
|
602
|
+
function Do() {
|
|
603
|
+
return Ok({});
|
|
604
|
+
}
|
|
605
|
+
//#endregion
|
|
457
606
|
//#region src/interop.ts
|
|
458
607
|
/**
|
|
459
608
|
* Bridge a nullable value into a {@link Result}: absence becomes a **modeled**
|
|
460
609
|
* `Err`. The sanctioned alternative to an `Option` type.
|
|
461
610
|
*
|
|
462
611
|
* @remarks
|
|
463
|
-
* `null` and `undefined` map to `
|
|
612
|
+
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
464
613
|
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
465
614
|
*
|
|
466
615
|
* @typeParam T - the (nullable) value type.
|
|
@@ -475,7 +624,7 @@ function isDefectMarker(x) {
|
|
|
475
624
|
* ```
|
|
476
625
|
*/
|
|
477
626
|
function fromNullable(value, onAbsent) {
|
|
478
|
-
return value === null || value === void 0 ?
|
|
627
|
+
return value === null || value === void 0 ? Err(onAbsent()) : Ok(value);
|
|
479
628
|
}
|
|
480
629
|
/**
|
|
481
630
|
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
|
@@ -483,14 +632,14 @@ function fromNullable(value, onAbsent) {
|
|
|
483
632
|
*
|
|
484
633
|
* @remarks
|
|
485
634
|
* `qualify` **must** triage every thrown cause into a modeled error `E` or a
|
|
486
|
-
* {@link Defect} (via {@link
|
|
635
|
+
* {@link Defect} (via {@link Defect}) — there is no path that leaves `unknown`
|
|
487
636
|
* in `E`. A throw inside `qualify` itself is treated as a `Defect`.
|
|
488
637
|
*
|
|
489
638
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
490
639
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
491
|
-
* `qualify` that returns *only* `
|
|
640
|
+
* `qualify` that returns *only* `Defect(cause)` yields `E = never` (a Defect is
|
|
492
641
|
* out-of-band and must not pollute the error channel); reach for
|
|
493
|
-
* {@link fromSafePromise} when every failure is a
|
|
642
|
+
* {@link fromSafePromise} when every failure is a Defect.
|
|
494
643
|
*
|
|
495
644
|
* @typeParam A - the wrapped function's argument tuple.
|
|
496
645
|
* @typeParam T - the wrapped function's return type.
|
|
@@ -502,8 +651,8 @@ function fromNullable(value, onAbsent) {
|
|
|
502
651
|
*
|
|
503
652
|
* @example
|
|
504
653
|
* ```ts
|
|
505
|
-
* import { fromThrowable,
|
|
506
|
-
* const parse = fromThrowable(JSON.parse, (cause) =>
|
|
654
|
+
* import { fromThrowable, Defect } from "unthrown";
|
|
655
|
+
* const parse = fromThrowable(JSON.parse, (cause) => Defect(cause));
|
|
507
656
|
* parse("{}").unwrap();
|
|
508
657
|
* ```
|
|
509
658
|
*/
|
|
@@ -511,7 +660,7 @@ function fromThrowable(fn, qualify) {
|
|
|
511
660
|
const triage = qualify;
|
|
512
661
|
return (...args) => {
|
|
513
662
|
try {
|
|
514
|
-
return
|
|
663
|
+
return Ok(fn(...args));
|
|
515
664
|
} catch (cause) {
|
|
516
665
|
return qualifyToResult(cause, triage);
|
|
517
666
|
}
|
|
@@ -529,8 +678,8 @@ function fromThrowable(fn, qualify) {
|
|
|
529
678
|
*
|
|
530
679
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
531
680
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
532
|
-
* `qualify` that returns *only* `
|
|
533
|
-
* rejection is a
|
|
681
|
+
* `qualify` that returns *only* `Defect(cause)` yields `E = never`; when every
|
|
682
|
+
* rejection is a Defect, prefer {@link fromSafePromise}.
|
|
534
683
|
*
|
|
535
684
|
* @typeParam T - the resolved value type.
|
|
536
685
|
* @typeParam R - `qualify`'s return type; the modeled error `E` is
|
|
@@ -540,9 +689,9 @@ function fromThrowable(fn, qualify) {
|
|
|
540
689
|
*
|
|
541
690
|
* @example
|
|
542
691
|
* ```ts
|
|
543
|
-
* import { fromPromise,
|
|
692
|
+
* import { fromPromise, Defect } from "unthrown";
|
|
544
693
|
* const user = await fromPromise(fetchUser(id), (cause) =>
|
|
545
|
-
* cause instanceof NotFoundError ? ("not_found" as const) :
|
|
694
|
+
* cause instanceof NotFoundError ? ("not_found" as const) : Defect(cause),
|
|
546
695
|
* );
|
|
547
696
|
* ```
|
|
548
697
|
*/
|
|
@@ -586,7 +735,7 @@ function foldArray(results) {
|
|
|
586
735
|
for (const r of results) if (r.tag === "Defect") firstDefect ??= r;
|
|
587
736
|
else if (r.tag === "Err") firstErr ??= r;
|
|
588
737
|
else values.push(r.value);
|
|
589
|
-
return firstDefect ?? firstErr ??
|
|
738
|
+
return firstDefect ?? firstErr ?? Ok(values);
|
|
590
739
|
}
|
|
591
740
|
/**
|
|
592
741
|
* Fold a record of settled `Result`s with the same rules, else `Ok` of the
|
|
@@ -607,7 +756,7 @@ function foldRecord(results) {
|
|
|
607
756
|
writable: true,
|
|
608
757
|
configurable: true
|
|
609
758
|
});
|
|
610
|
-
return firstDefect ?? firstErr ??
|
|
759
|
+
return firstDefect ?? firstErr ?? Ok(values);
|
|
611
760
|
}
|
|
612
761
|
/**
|
|
613
762
|
* Collect a tuple/array of {@link Result}s into a single `Result` of all their
|
|
@@ -616,16 +765,16 @@ function foldRecord(results) {
|
|
|
616
765
|
* @remarks
|
|
617
766
|
* Short-circuits on the **first** `Err` (later entries are not inspected for
|
|
618
767
|
* their error); any `Defect` present **dominates**, winning even over an earlier
|
|
619
|
-
* `Err`. A **fixed tuple** keeps its positional types — `all([
|
|
768
|
+
* `Err`. A **fixed tuple** keeps its positional types — `all([Ok(1), Ok("a")])`
|
|
620
769
|
* is `Result<[number, string], …>` — while a **dynamic array** `Result<T, E>[]`
|
|
621
770
|
* collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
|
|
622
771
|
* use {@link allFromDict}.
|
|
623
772
|
*
|
|
624
773
|
* @example
|
|
625
774
|
* ```ts
|
|
626
|
-
* import { all,
|
|
627
|
-
* all([
|
|
628
|
-
* all([
|
|
775
|
+
* import { all, Ok } from "unthrown";
|
|
776
|
+
* all([Ok(1), Ok("a"), Ok(true)]).unwrap(); // [1, "a", true] (typed [number, string, boolean])
|
|
777
|
+
* all([Ok(1), Ok(2)] as Result<number, never>[]).unwrap(); // number[]
|
|
629
778
|
* ```
|
|
630
779
|
*/
|
|
631
780
|
function all(results) {
|
|
@@ -643,8 +792,8 @@ function all(results) {
|
|
|
643
792
|
*
|
|
644
793
|
* @example
|
|
645
794
|
* ```ts
|
|
646
|
-
* import { allFromDict,
|
|
647
|
-
* allFromDict({ id:
|
|
795
|
+
* import { allFromDict, Ok } from "unthrown";
|
|
796
|
+
* allFromDict({ id: Ok(1), name: Ok("ada") }).unwrap(); // { id: 1, name: "ada" }
|
|
648
797
|
* ```
|
|
649
798
|
*/
|
|
650
799
|
function allFromDict(results) {
|
|
@@ -697,29 +846,30 @@ function allFromDictAsync(results) {
|
|
|
697
846
|
//#region src/facade.ts
|
|
698
847
|
/**
|
|
699
848
|
* Companion object grouping the standalone entry points under a single,
|
|
700
|
-
* discoverable namespace: {@link Result.
|
|
701
|
-
* {@link Result.
|
|
849
|
+
* discoverable namespace: {@link Result.Ok}, {@link Result.Err},
|
|
850
|
+
* {@link Result.Defect}, {@link Result.fromNullable}, {@link Result.fromThrowable},
|
|
702
851
|
* {@link Result.fromPromise}, {@link Result.fromSafePromise}, {@link Result.all},
|
|
703
852
|
* {@link Result.allAsync}, {@link Result.allFromDict},
|
|
704
853
|
* {@link Result.allFromDictAsync}, {@link Result.isOk}, {@link Result.isErr},
|
|
705
|
-
* {@link Result.isDefect}.
|
|
854
|
+
* {@link Result.isDefect}, {@link Result.isResult}.
|
|
706
855
|
*
|
|
707
856
|
* @remarks
|
|
708
857
|
* Purely additive sugar — each member **is** the corresponding free function.
|
|
709
858
|
* The free functions remain the primary, tree-shakeable API; importing only
|
|
710
|
-
* `{
|
|
859
|
+
* `{ Ok }` never pulls this object in. The value `Result` and the type
|
|
711
860
|
* {@link Result} share one name (the companion-object pattern).
|
|
712
861
|
*
|
|
713
862
|
* @example
|
|
714
863
|
* ```ts
|
|
715
864
|
* import { Result } from "unthrown";
|
|
716
|
-
* Result.
|
|
865
|
+
* Result.Ok(1).flatMap((n) => Result.Ok(n + 1)).unwrap(); // 2
|
|
717
866
|
* ```
|
|
718
867
|
*/
|
|
719
868
|
const Result = {
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
869
|
+
Ok,
|
|
870
|
+
Err,
|
|
871
|
+
Defect,
|
|
872
|
+
Do,
|
|
723
873
|
fromNullable,
|
|
724
874
|
fromThrowable,
|
|
725
875
|
fromPromise,
|
|
@@ -730,7 +880,8 @@ const Result = {
|
|
|
730
880
|
allFromDictAsync,
|
|
731
881
|
isOk,
|
|
732
882
|
isErr,
|
|
733
|
-
isDefect
|
|
883
|
+
isDefect,
|
|
884
|
+
isResult
|
|
734
885
|
};
|
|
735
886
|
//#endregion
|
|
736
887
|
//#region src/tagged.ts
|
|
@@ -800,6 +951,10 @@ function matchTags(result, handlers) {
|
|
|
800
951
|
});
|
|
801
952
|
}
|
|
802
953
|
//#endregion
|
|
954
|
+
exports.Defect = Defect;
|
|
955
|
+
exports.Do = Do;
|
|
956
|
+
exports.Err = Err;
|
|
957
|
+
exports.Ok = Ok;
|
|
803
958
|
exports.Result = Result;
|
|
804
959
|
exports.TaggedError = TaggedError;
|
|
805
960
|
exports.UnwrapError = UnwrapError;
|
|
@@ -807,8 +962,6 @@ exports.all = all;
|
|
|
807
962
|
exports.allAsync = allAsync;
|
|
808
963
|
exports.allFromDict = allFromDict;
|
|
809
964
|
exports.allFromDictAsync = allFromDictAsync;
|
|
810
|
-
exports.defect = defect;
|
|
811
|
-
exports.err = err;
|
|
812
965
|
exports.fromNullable = fromNullable;
|
|
813
966
|
exports.fromPromise = fromPromise;
|
|
814
967
|
exports.fromSafePromise = fromSafePromise;
|
|
@@ -816,5 +969,5 @@ exports.fromThrowable = fromThrowable;
|
|
|
816
969
|
exports.isDefect = isDefect;
|
|
817
970
|
exports.isErr = isErr;
|
|
818
971
|
exports.isOk = isOk;
|
|
972
|
+
exports.isResult = isResult;
|
|
819
973
|
exports.matchTags = matchTags;
|
|
820
|
-
exports.ok = ok;
|