unthrown 5.8.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 +497 -72
- package/dist/index.d.cts +1789 -1468
- package/dist/index.d.mts +1789 -1468
- package/dist/index.mjs +494 -73
- package/package.json +6 -6
package/dist/index.cjs
CHANGED
|
@@ -18,24 +18,63 @@ const PATTERN_BRAND = Symbol.for("unthrown.matcher.pattern");
|
|
|
18
18
|
* is a bug).
|
|
19
19
|
*
|
|
20
20
|
* @category Errors
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* import { match, NonExhaustiveError } from "unthrown";
|
|
25
|
+
*
|
|
26
|
+
* // A value typed "a" | "b" that is really "c" (a cast, a raw-JS caller):
|
|
27
|
+
* const rogue = "c" as "a" | "b";
|
|
28
|
+
* try {
|
|
29
|
+
* match(rogue)
|
|
30
|
+
* .with("a", () => 1)
|
|
31
|
+
* .with("b", () => 2)
|
|
32
|
+
* .exhaustive();
|
|
33
|
+
* } catch (error) {
|
|
34
|
+
* error instanceof NonExhaustiveError; // => true
|
|
35
|
+
* (error as NonExhaustiveError).input; // => "c"
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
21
38
|
*/
|
|
22
39
|
var NonExhaustiveError = class extends Error {
|
|
23
40
|
/** The value no arm matched. */
|
|
24
41
|
input;
|
|
25
42
|
constructor(input) {
|
|
26
|
-
|
|
27
|
-
try {
|
|
28
|
-
printed = JSON.stringify(input) ?? String(input);
|
|
29
|
-
} catch {
|
|
30
|
-
printed = String(input);
|
|
31
|
-
}
|
|
32
|
-
super(`unthrown: no pattern matched the value ${printed}`);
|
|
43
|
+
super(`unthrown: no pattern matched the value ${printValue(input)}`);
|
|
33
44
|
this.name = "NonExhaustiveError";
|
|
34
45
|
this.input = input;
|
|
35
46
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
36
47
|
}
|
|
37
48
|
};
|
|
38
49
|
/**
|
|
50
|
+
* Render a rogue value for {@link NonExhaustiveError}'s message — **total**,
|
|
51
|
+
* because the error is constructed inside the throw → defect net and a throw
|
|
52
|
+
* here would replace the diagnostic with an unrelated one (or escape `match`).
|
|
53
|
+
*
|
|
54
|
+
* @remarks
|
|
55
|
+
* Each step can fail for a different input, so each is guarded: `JSON.stringify`
|
|
56
|
+
* RETURNS undefined for a function, a symbol or `undefined` and throws for a
|
|
57
|
+
* bigint or a circular object; `String()` throws for a null-prototype object or
|
|
58
|
+
* a hostile `toString` / `Symbol.toPrimitive`; `Object.prototype.toString`
|
|
59
|
+
* throws only for a Proxy whose `get` trap does. The last resort is a constant.
|
|
60
|
+
*
|
|
61
|
+
* @internal
|
|
62
|
+
*/
|
|
63
|
+
function printValue(input) {
|
|
64
|
+
try {
|
|
65
|
+
const json = JSON.stringify(input);
|
|
66
|
+
if (json !== void 0) return json;
|
|
67
|
+
} catch {}
|
|
68
|
+
try {
|
|
69
|
+
return String(input);
|
|
70
|
+
} catch {}
|
|
71
|
+
try {
|
|
72
|
+
return Object.prototype.toString.call(input);
|
|
73
|
+
} catch {
|
|
74
|
+
return "<unprintable value>";
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
39
78
|
* Is `x` a *plain* object (prototype `Object.prototype` or `null`) — an object
|
|
40
79
|
* literal, the only object shape that acts as a structural pattern?
|
|
41
80
|
*
|
|
@@ -123,6 +162,24 @@ Object.freeze(MatcherImpl.prototype);
|
|
|
123
162
|
* {@link P}).
|
|
124
163
|
*
|
|
125
164
|
* @category Constructors
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
* ```ts
|
|
168
|
+
* import { match, type Result } from "unthrown";
|
|
169
|
+
*
|
|
170
|
+
* // Matching a whole Result natively — every variant named, `.exhaustive()` last:
|
|
171
|
+
* declare const r: Result<number, "odd" | "negative">;
|
|
172
|
+
* const label = match(r)
|
|
173
|
+
* .with({ tag: "Ok" }, (ok) => `got ${ok.value}`)
|
|
174
|
+
* .with({ tag: "Err" }, (err) => `failed: ${err.error}`)
|
|
175
|
+
* .with({ tag: "Defect" }, () => "bug")
|
|
176
|
+
* .exhaustive();
|
|
177
|
+
*
|
|
178
|
+
* // Inside a combinator, return the un-terminated builder — it runs `.exhaustive()`:
|
|
179
|
+
* const reason = r.mapErrCases((matcher) =>
|
|
180
|
+
* matcher.with("odd", () => "not even" as const).with("negative", () => "below zero" as const),
|
|
181
|
+
* ); // Result<number, "not even" | "below zero">
|
|
182
|
+
* ```
|
|
126
183
|
*/
|
|
127
184
|
function match(value) {
|
|
128
185
|
return new MatcherImpl(value);
|
|
@@ -156,17 +213,49 @@ const universal = pattern(() => true);
|
|
|
156
213
|
* (`.with(P.tag("A"), P.tag("B"), handler)`).
|
|
157
214
|
* - `P.instanceOf(Cls)` — an `instanceof` check, narrowing to the class
|
|
158
215
|
* instance type (for union members that are not tagged, e.g. a third-party
|
|
159
|
-
* error class).
|
|
216
|
+
* error class). **Exhaustiveness here is structural, the check is not:**
|
|
217
|
+
* two classes with the same shape (`class A extends Error {}`,
|
|
218
|
+
* `class B extends Error {}`) are one type to the compiler, so a match
|
|
219
|
+
* naming only `A` compiles as exhaustive while a `B` fails `instanceof A`
|
|
220
|
+
* at runtime and becomes a `Defect`. Give each class a distinguishing field
|
|
221
|
+
* (a `readonly kind = "A"` literal) or use `TaggedError`, and the missing
|
|
222
|
+
* arm is a compile error again.
|
|
160
223
|
* - `P.when(guard)` — an arbitrary type-guard predicate. Also the way to match
|
|
161
224
|
* a primitive shape (`P.when((v): v is string => typeof v === "string")`),
|
|
162
225
|
* and grouping patterns under one handler is what a `.with(a, b, handler)`
|
|
163
226
|
* arm already does.
|
|
164
227
|
*
|
|
165
228
|
* @category Constructors
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* import { P, TaggedError, type Result } from "unthrown";
|
|
233
|
+
*
|
|
234
|
+
* class NotFound extends TaggedError("NotFound")<{ id: string }> {}
|
|
235
|
+
* class Conflict extends TaggedError("Conflict") {}
|
|
236
|
+
* class VendorTimeout extends Error {
|
|
237
|
+
* readonly afterMs = 30_000;
|
|
238
|
+
* }
|
|
239
|
+
*
|
|
240
|
+
* declare const r: Result<string, NotFound | Conflict | VendorTimeout | "rate_limited">;
|
|
241
|
+
* const status = r.match({
|
|
242
|
+
* ok: () => 200,
|
|
243
|
+
* errCases: (matcher) =>
|
|
244
|
+
* matcher
|
|
245
|
+
* .with(P.tag("NotFound"), () => 404) // a TaggedError, narrowed with its payload
|
|
246
|
+
* .with(P.tag("Conflict"), () => 409)
|
|
247
|
+
* .with(P.instanceOf(VendorTimeout), (e) => (e.afterMs > 10_000 ? 504 : 503))
|
|
248
|
+
* .with(
|
|
249
|
+
* P.when((v): v is "rate_limited" => v === "rate_limited"),
|
|
250
|
+
* () => 429,
|
|
251
|
+
* ),
|
|
252
|
+
* defect: () => 500,
|
|
253
|
+
* });
|
|
254
|
+
* ```
|
|
166
255
|
*/
|
|
167
256
|
const P = Object.freeze({
|
|
168
257
|
_: universal,
|
|
169
|
-
tag: (value) => ({ _tag: value }),
|
|
258
|
+
tag: (value) => Object.freeze({ _tag: value }),
|
|
170
259
|
instanceOf: (cls) => pattern((value) => value instanceof cls),
|
|
171
260
|
when: (guard) => pattern(guard)
|
|
172
261
|
});
|
|
@@ -328,6 +417,7 @@ var Res = class {
|
|
|
328
417
|
try {
|
|
329
418
|
const out = runMatch(f, this.error);
|
|
330
419
|
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
420
|
+
if (isThenable(out)) return asyncBranchDefect(out);
|
|
331
421
|
return errRes(out);
|
|
332
422
|
} catch (cause) {
|
|
333
423
|
return defectRes(cause);
|
|
@@ -349,6 +439,7 @@ var Res = class {
|
|
|
349
439
|
try {
|
|
350
440
|
const out = runMatch(f, this.error);
|
|
351
441
|
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
442
|
+
if (isThenable(out)) return asyncBranchDefect(out);
|
|
352
443
|
return okRes(out);
|
|
353
444
|
} catch (cause) {
|
|
354
445
|
return defectRes(cause);
|
|
@@ -518,7 +609,8 @@ function defectRes(cause) {
|
|
|
518
609
|
* brand the prototype carries — so a `Result` built by **another copy** of
|
|
519
610
|
* unthrown, e.g. the CJS and ESM builds loaded side by side, is still
|
|
520
611
|
* recognised). A look-alike plain object (`{ tag: "Ok" }`) carries neither and
|
|
521
|
-
* is **not** matched
|
|
612
|
+
* is **not** matched; nor is a forgery built on the real prototype whose `tag` or
|
|
613
|
+
* payload is a getter (both must be own data properties). An `AsyncResult` is not a `Result` and returns `false`.
|
|
522
614
|
*
|
|
523
615
|
* @returns `true` when `x` is a `Result` produced by this library.
|
|
524
616
|
*
|
|
@@ -545,13 +637,42 @@ function defectRes(cause) {
|
|
|
545
637
|
* @category Guards
|
|
546
638
|
*/
|
|
547
639
|
function isResult(x) {
|
|
548
|
-
if (x instanceof Res) return true;
|
|
549
640
|
try {
|
|
550
|
-
return (typeof x === "object" || typeof x === "function") && x !== null && Reflect.get(x, RESULT_BRAND) === true;
|
|
641
|
+
return (x instanceof Res || (typeof x === "object" || typeof x === "function") && x !== null && Reflect.get(x, RESULT_BRAND) === true) && hasOwnVariantShape(x);
|
|
551
642
|
} catch {
|
|
552
643
|
return false;
|
|
553
644
|
}
|
|
554
645
|
}
|
|
646
|
+
/** Each variant's payload key. @internal */
|
|
647
|
+
const PAYLOAD_KEY = {
|
|
648
|
+
Ok: "value",
|
|
649
|
+
Err: "error",
|
|
650
|
+
Defect: "cause"
|
|
651
|
+
};
|
|
652
|
+
/**
|
|
653
|
+
* Does `x` carry a variant's shape as own **data** properties — `tag` one of
|
|
654
|
+
* the three variants, plus that variant's payload key?
|
|
655
|
+
*
|
|
656
|
+
* @remarks
|
|
657
|
+
* A brand is not enough: the prototype (and so the brand) is reachable from any
|
|
658
|
+
* genuine `Result`, so `Object.create(protoOf(Ok(1)))` with a throwing `tag` or
|
|
659
|
+
* payload getter passed the guard and then threw — raw out of `all`, or as a
|
|
660
|
+
* rejection out of an `AsyncResult` that must never reject. Every genuine
|
|
661
|
+
* `Result`, from any copy of the library, is a frozen object literal whose `tag`
|
|
662
|
+
* and payload are own data properties, so reading their descriptors (which
|
|
663
|
+
* never runs a getter) accepts all of them and no getter-bearing forgery.
|
|
664
|
+
*
|
|
665
|
+
* @internal
|
|
666
|
+
*/
|
|
667
|
+
function hasOwnVariantShape(x) {
|
|
668
|
+
const tag = Object.getOwnPropertyDescriptor(x, "tag");
|
|
669
|
+
if (tag === void 0 || !("value" in tag)) return false;
|
|
670
|
+
const variant = tag.value;
|
|
671
|
+
if (variant !== "Ok" && variant !== "Err" && variant !== "Defect") return false;
|
|
672
|
+
const key = PAYLOAD_KEY[variant];
|
|
673
|
+
const payload = Object.getOwnPropertyDescriptor(x, key);
|
|
674
|
+
return payload !== void 0 && "value" in payload;
|
|
675
|
+
}
|
|
555
676
|
/**
|
|
556
677
|
* Reuse a non-matching variant (an `Err` or `Defect`) as a differently-typed
|
|
557
678
|
* `Result`, with no runtime work. Sound because the passed-through variant
|
|
@@ -577,9 +698,9 @@ function passThrough(self) {
|
|
|
577
698
|
* - inside a `try` that routes the throw to a `Defect` (the boundaries in
|
|
578
699
|
* `interop.ts`, where a hostile value arriving at a triage point *is* an
|
|
579
700
|
* unmodeled failure and should surface as one); or
|
|
580
|
-
* -
|
|
581
|
-
*
|
|
582
|
-
* drop it without throwing.
|
|
701
|
+
* - to classify a value that is then **discarded** (a `Defect` minted, the
|
|
702
|
+
* value handed to {@link silenceIfThenable}), where the only correct answer
|
|
703
|
+
* is to drop it without throwing — and without starting a lazy thenable.
|
|
583
704
|
*
|
|
584
705
|
* Calling it bare, outside both, is a bug: the throw escapes into whatever
|
|
585
706
|
* context invoked it.
|
|
@@ -590,27 +711,37 @@ function isThenable(x) {
|
|
|
590
711
|
return (typeof x === "object" || typeof x === "function") && x !== null && typeof x.then === "function";
|
|
591
712
|
}
|
|
592
713
|
/**
|
|
593
|
-
*
|
|
714
|
+
* Silence a **genuine `Promise`** a combinator or boundary is about to
|
|
715
|
+
* **discard** — and leave every other thenable untouched.
|
|
594
716
|
*
|
|
595
717
|
* @remarks
|
|
596
718
|
* The observers (`tap`, `tapErrCases`, `tapDefect`, `tapFailure`) throw their
|
|
597
719
|
* callback's return value away, and the `Result`-returning combinators reject a
|
|
598
|
-
* non-`Result` one. Either way, a
|
|
720
|
+
* non-`Result` one. Either way, a promise that slipped past `NotThenable` (a
|
|
599
721
|
* cast, a raw-JS caller) is dropped while still in flight — and if it later
|
|
600
722
|
* rejects, nothing is holding it, so the rejection floats unhandled and takes
|
|
601
723
|
* the process down on Node by default. Worse for an observer: its whole job is
|
|
602
724
|
* to make a failure visible, and this is the one path where the failure is
|
|
603
|
-
* invisible.
|
|
725
|
+
* invisible. Attaching a no-op rejection handler costs nothing and changes no
|
|
726
|
+
* outcome.
|
|
604
727
|
*
|
|
605
|
-
*
|
|
606
|
-
*
|
|
607
|
-
*
|
|
728
|
+
* Only a `Promise` **instance** is touched. A promise is already running, so
|
|
729
|
+
* handling its rejection starts nothing; a non-`Promise` thenable may be
|
|
730
|
+
* **lazy** — a `PrismaPromise`, a query builder — whose work begins only when
|
|
731
|
+
* `then` is called. Adopting one (`Promise.resolve(x)` calls `x.then`) would
|
|
732
|
+
* *run* the effect the caller is being told was refused: `fromSafeThrowable(()
|
|
733
|
+
* => prisma.user.deleteMany())` returned a `Defect` and deleted the rows
|
|
734
|
+
* anyway. A lazy thenable that is never started cannot reject, so there is
|
|
735
|
+
* nothing to silence. Callers still *classify* any thenable (a `Defect` where
|
|
736
|
+
* the spec says so) via {@link isThenable}; this only decides what to adopt.
|
|
737
|
+
*
|
|
738
|
+
* Total: a hostile `then` getter or `Symbol.hasInstance` path is swallowed.
|
|
608
739
|
*
|
|
609
740
|
* @internal
|
|
610
741
|
*/
|
|
611
742
|
function silenceIfThenable(value) {
|
|
612
743
|
try {
|
|
613
|
-
if (
|
|
744
|
+
if (value instanceof Promise) value.then(void 0, () => void 0);
|
|
614
745
|
} catch {}
|
|
615
746
|
}
|
|
616
747
|
/**
|
|
@@ -628,6 +759,19 @@ function nonResultCallbackDefect(returned) {
|
|
|
628
759
|
return defectRes(/* @__PURE__ */ new TypeError("unthrown: a combinator callback returned a non-Result value"));
|
|
629
760
|
}
|
|
630
761
|
/**
|
|
762
|
+
* The Defect minted when a non-awaiting error transformer (`mapErrCases` /
|
|
763
|
+
* `recoverErrCases`) gets a thenable branch output past the compile-time ban
|
|
764
|
+
* (a cast, an untyped caller): never `Err(<Promise>)` / `Ok(<Promise>)` — a
|
|
765
|
+
* Promise in the channel is un-triaged — and a genuine Promise is silenced so
|
|
766
|
+
* its rejection cannot float. The sibling of the aggregates' async-`merge` net.
|
|
767
|
+
*
|
|
768
|
+
* @internal
|
|
769
|
+
*/
|
|
770
|
+
function asyncBranchDefect(out) {
|
|
771
|
+
silenceIfThenable(out);
|
|
772
|
+
return defectRes(/* @__PURE__ */ new TypeError("unthrown: mapErrCases/recoverErrCases branches must be SYNCHRONOUS, but one returned a thenable — lift async work with fromPromise and use flatMapErrCases"));
|
|
773
|
+
}
|
|
774
|
+
/**
|
|
631
775
|
* Drive an error-combinator callback: build `match(error)`, hand it (plus the
|
|
632
776
|
* injected `defect`) to the callback, and `.run()` the returned exhaustive
|
|
633
777
|
* builder to its output. `.run()` executes `.exhaustive()` — type-forced
|
|
@@ -676,10 +820,23 @@ function observerThrowToDefect(thrown, original) {
|
|
|
676
820
|
* @internal
|
|
677
821
|
*/
|
|
678
822
|
function scopeOf(value) {
|
|
679
|
-
if (typeof value !== "object" || value === null ||
|
|
823
|
+
if (typeof value !== "object" || value === null || !isPlainScope(value)) throw new TypeError("bind/let requires a plain object scope — start a do-chain with Do()");
|
|
680
824
|
return value;
|
|
681
825
|
}
|
|
682
826
|
/**
|
|
827
|
+
* A *plain* object: its prototype is `null` or an `Object.prototype` (any
|
|
828
|
+
* realm's — recognised by having no prototype of its own). The spread that
|
|
829
|
+
* merges a `bind`/`let` key copies only own enumerable data, so a class
|
|
830
|
+
* instance's getters and prototype methods — and an array's identity — would
|
|
831
|
+
* silently vanish while the type still claimed them.
|
|
832
|
+
*
|
|
833
|
+
* @internal
|
|
834
|
+
*/
|
|
835
|
+
function isPlainScope(value) {
|
|
836
|
+
const proto = Object.getPrototypeOf(value);
|
|
837
|
+
return proto === null || Object.getPrototypeOf(proto) === null;
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
683
840
|
* The sole runtime implementation of {@link AsyncResult}: wraps a
|
|
684
841
|
* `Promise<Result>` constructed never to reject. Operates on the public `Result`
|
|
685
842
|
* union (via `tag`), never on `Res` internals. Never re-exported from `index.ts`.
|
|
@@ -769,7 +926,7 @@ var AsyncRes = class AsyncRes {
|
|
|
769
926
|
ensure(predicate, onFail) {
|
|
770
927
|
return this.#lift((r) => r.ensure(predicate, onFail));
|
|
771
928
|
}
|
|
772
|
-
mapErrCases(f) {
|
|
929
|
+
mapErrCases(f, ..._guard) {
|
|
773
930
|
return this.#lift((r) => r.mapErrCases(f));
|
|
774
931
|
}
|
|
775
932
|
flatMapErrCases(f) {
|
|
@@ -786,7 +943,7 @@ var AsyncRes = class AsyncRes {
|
|
|
786
943
|
}
|
|
787
944
|
}));
|
|
788
945
|
}
|
|
789
|
-
recoverErrCases(f) {
|
|
946
|
+
recoverErrCases(f, ..._guard) {
|
|
790
947
|
return this.#lift((r) => r.recoverErrCases(f));
|
|
791
948
|
}
|
|
792
949
|
tapErrCases(f) {
|
|
@@ -1124,12 +1281,17 @@ function fromNullable(value, onAbsent) {
|
|
|
1124
1281
|
function fromThrowable(fn, qualify) {
|
|
1125
1282
|
const triage = qualify;
|
|
1126
1283
|
return (...args) => {
|
|
1284
|
+
let value;
|
|
1127
1285
|
try {
|
|
1128
|
-
|
|
1129
|
-
return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
|
|
1286
|
+
value = fn(...args);
|
|
1130
1287
|
} catch (cause) {
|
|
1131
1288
|
return qualifyToResult(cause, triage);
|
|
1132
1289
|
}
|
|
1290
|
+
try {
|
|
1291
|
+
return isThenable(value) ? thenableReturnDefect(value, SYNC_FN_THENABLE) : Ok(value);
|
|
1292
|
+
} catch (cause) {
|
|
1293
|
+
return defectRes(cause);
|
|
1294
|
+
}
|
|
1133
1295
|
};
|
|
1134
1296
|
}
|
|
1135
1297
|
/**
|
|
@@ -1169,7 +1331,7 @@ function fromSafeThrowable(fn) {
|
|
|
1169
1331
|
return (...args) => {
|
|
1170
1332
|
try {
|
|
1171
1333
|
const value = fn(...args);
|
|
1172
|
-
return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
|
|
1334
|
+
return isThenable(value) ? thenableReturnDefect(value, SYNC_FN_THENABLE) : Ok(value);
|
|
1173
1335
|
} catch (cause) {
|
|
1174
1336
|
return defectRes(cause);
|
|
1175
1337
|
}
|
|
@@ -1311,7 +1473,7 @@ function fromExecutor(executor) {
|
|
|
1311
1473
|
};
|
|
1312
1474
|
try {
|
|
1313
1475
|
const returned = executor(settle, defect);
|
|
1314
|
-
if (
|
|
1476
|
+
if (returned instanceof Promise) returned.then(void 0, (cause) => settle(defect(cause)));
|
|
1315
1477
|
} catch (cause) {
|
|
1316
1478
|
settle(defect(cause));
|
|
1317
1479
|
}
|
|
@@ -1322,7 +1484,7 @@ function qualifyToResult(cause, qualify) {
|
|
|
1322
1484
|
const q = qualify(cause, defect);
|
|
1323
1485
|
if (isDefectMarker(q)) return defectRes(q.cause);
|
|
1324
1486
|
if (isThenable(q)) {
|
|
1325
|
-
|
|
1487
|
+
silenceIfThenable(q);
|
|
1326
1488
|
return defectRes(/* @__PURE__ */ new TypeError("unthrown: qualify must be synchronous — it returned a thenable; triage the cause without awaiting"));
|
|
1327
1489
|
}
|
|
1328
1490
|
return errRes(q);
|
|
@@ -1331,30 +1493,78 @@ function qualifyToResult(cause, qualify) {
|
|
|
1331
1493
|
}
|
|
1332
1494
|
}
|
|
1333
1495
|
/**
|
|
1334
|
-
* The
|
|
1335
|
-
*
|
|
1336
|
-
*
|
|
1496
|
+
* The message for {@link thenableReturnDefect} at a **synchronous boundary** —
|
|
1497
|
+
* an `async` function handed to {@link fromThrowable} / {@link fromSafeThrowable}.
|
|
1498
|
+
*
|
|
1499
|
+
* @internal
|
|
1500
|
+
*/
|
|
1501
|
+
const SYNC_FN_THENABLE = "unthrown: fromThrowable/fromSafeThrowable wrap a SYNCHRONOUS function, but `fn` returned a thenable — its rejection would escape qualification. Use fromPromise/fromSafePromise instead.";
|
|
1502
|
+
/**
|
|
1503
|
+
* The message for {@link thenableReturnDefect} in an **accumulating aggregate** —
|
|
1504
|
+
* an `async` `merge` handed to {@link validateAll} and friends.
|
|
1505
|
+
*
|
|
1506
|
+
* @internal
|
|
1507
|
+
*/
|
|
1508
|
+
const MERGE_THENABLE = "unthrown: an accumulating aggregate's `merge` must be SYNCHRONOUS, but it returned a thenable — its rejection would escape qualification.";
|
|
1509
|
+
/**
|
|
1510
|
+
* The Defect minted where a callback that must be **synchronous** returned a
|
|
1511
|
+
* thenable: a `fn` handed to {@link fromThrowable} / {@link fromSafeThrowable},
|
|
1512
|
+
* or a `merge` handed to an accumulating aggregate.
|
|
1337
1513
|
*
|
|
1338
1514
|
* @remarks
|
|
1339
1515
|
* This is the sibling of the thenable-`qualify` net in {@link qualifyToResult},
|
|
1340
1516
|
* and it closes a strictly worse hole. A synchronous boundary only ever sees a
|
|
1341
1517
|
* synchronous `throw`, so an async `fn`'s rejection never reaches `qualify` at
|
|
1342
1518
|
* all: it would sit inside `Ok(<Promise>)`, un-triaged, and then float as an
|
|
1343
|
-
* unhandled rejection — which terminates the process on Node by default.
|
|
1519
|
+
* unhandled rejection — which terminates the process on Node by default. An
|
|
1520
|
+
* async `merge` is the same hole one channel over: `Err(<Promise>)`.
|
|
1521
|
+
*
|
|
1522
|
+
* The `fn` case cannot be banned at compile time without collateral damage:
|
|
1523
|
+
* `T & NotThenable<T>` on `fn`'s return makes a **generic** function
|
|
1524
|
+
* unassignable, so `fromSafeThrowable(structuredClone)` stops compiling and `T`
|
|
1525
|
+
* collapses to `unknown`. (The phantom rest-tuple guard `fromPromise` uses fares
|
|
1526
|
+
* worse.) `merge` *is* `NotThenable`-constrained, but a cast or an untyped
|
|
1527
|
+
* caller still reaches here. Either way the runtime answer is the same, and it
|
|
1528
|
+
* costs nothing: a Defect, plus a no-op rejection handler so an orphaned Promise
|
|
1529
|
+
* cannot float.
|
|
1344
1530
|
*
|
|
1345
|
-
*
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1531
|
+
* @internal
|
|
1532
|
+
*/
|
|
1533
|
+
function thenableReturnDefect(value, message) {
|
|
1534
|
+
silenceIfThenable(value);
|
|
1535
|
+
return defectRes(new TypeError(message));
|
|
1536
|
+
}
|
|
1537
|
+
/**
|
|
1538
|
+
* A record's own **enumerable** keys — strings and symbols — with their values.
|
|
1539
|
+
*
|
|
1540
|
+
* @remarks
|
|
1541
|
+
* `Object.keys` / `Object.values` skip symbol keys, so a symbol-keyed `Err`
|
|
1542
|
+
* silently vanished from the fold while the types (`keyof R` includes it)
|
|
1543
|
+
* promised otherwise. `Reflect.ownKeys` filtered to the enumerable ones is
|
|
1544
|
+
* `Object.keys` plus symbols, in the same order (strings first, then symbols).
|
|
1545
|
+
* May throw on an out-of-contract container (`null`, a throwing getter) — every
|
|
1546
|
+
* caller routes that to a `Defect`.
|
|
1547
|
+
*
|
|
1548
|
+
* @internal
|
|
1549
|
+
*/
|
|
1550
|
+
function ownEntries(record) {
|
|
1551
|
+
const keys = Reflect.ownKeys(record).filter((key) => Object.prototype.propertyIsEnumerable.call(record, key));
|
|
1552
|
+
return [keys, keys.map((key) => record[key])];
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* Build an async aggregate's settled promise, turning a synchronous throw while
|
|
1556
|
+
* reading an out-of-contract container (`allAsync(undefined)`, a throwing
|
|
1557
|
+
* getter) into a `Defect` — the returned `AsyncResult` still never rejects and
|
|
1558
|
+
* the call never throws.
|
|
1352
1559
|
*
|
|
1353
1560
|
* @internal
|
|
1354
1561
|
*/
|
|
1355
|
-
function
|
|
1356
|
-
|
|
1357
|
-
|
|
1562
|
+
function settleOrDefect(build) {
|
|
1563
|
+
try {
|
|
1564
|
+
return new AsyncRes(build());
|
|
1565
|
+
} catch (cause) {
|
|
1566
|
+
return new AsyncRes(Promise.resolve(defectRes(cause)));
|
|
1567
|
+
}
|
|
1358
1568
|
}
|
|
1359
1569
|
/**
|
|
1360
1570
|
* Fold an array of settled `Result`s: first `Err` wins, any `Defect` dominates,
|
|
@@ -1366,22 +1576,46 @@ function thenableReturnDefect(value) {
|
|
|
1366
1576
|
function nonResultDefect() {
|
|
1367
1577
|
return defectRes(/* @__PURE__ */ new TypeError("unthrown: aggregate received a non-Result element"));
|
|
1368
1578
|
}
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1579
|
+
/**
|
|
1580
|
+
* Resolve every input concurrently (order preserved), adopting each one
|
|
1581
|
+
* defensively: a cast/untyped rejecting thenable becomes a `Defect` rather than
|
|
1582
|
+
* rejecting the internal promise, so the "an `AsyncResult`'s internal promise
|
|
1583
|
+
* never rejects" invariant holds even for out-of-contract input.
|
|
1584
|
+
*
|
|
1585
|
+
* @internal
|
|
1586
|
+
*/
|
|
1587
|
+
function settleAll(results) {
|
|
1588
|
+
return Promise.all(results.map((r) => Promise.resolve(r).then((x) => x, (cause) => defectRes(cause))));
|
|
1589
|
+
}
|
|
1590
|
+
function foldArray(results, merge) {
|
|
1591
|
+
try {
|
|
1592
|
+
let firstErr;
|
|
1593
|
+
let firstDefect;
|
|
1594
|
+
const values = [];
|
|
1595
|
+
const errors = [];
|
|
1596
|
+
for (const [i, r] of results.entries()) {
|
|
1597
|
+
if (!isResult(r)) {
|
|
1598
|
+
firstDefect ??= nonResultDefect();
|
|
1599
|
+
break;
|
|
1600
|
+
}
|
|
1601
|
+
if (r.tag === "Defect") {
|
|
1602
|
+
firstDefect ??= r;
|
|
1603
|
+
break;
|
|
1604
|
+
} else if (r.tag === "Err") {
|
|
1605
|
+
if (merge) errors.push([i, r.error]);
|
|
1606
|
+
else firstErr ??= r;
|
|
1607
|
+
} else values.push(r.value);
|
|
1377
1608
|
}
|
|
1378
|
-
if (
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1609
|
+
if (firstDefect) return firstDefect;
|
|
1610
|
+
if (merge && errors.length > 0) {
|
|
1611
|
+
const merged = merge(errors);
|
|
1612
|
+
if (isThenable(merged)) return thenableReturnDefect(merged, MERGE_THENABLE);
|
|
1613
|
+
return Err(merged);
|
|
1614
|
+
}
|
|
1615
|
+
return firstErr ?? Ok(values);
|
|
1616
|
+
} catch (cause) {
|
|
1617
|
+
return defectRes(cause);
|
|
1383
1618
|
}
|
|
1384
|
-
return firstDefect ?? firstErr ?? Ok(values);
|
|
1385
1619
|
}
|
|
1386
1620
|
/**
|
|
1387
1621
|
* Fold a record of settled `Result`s with the same rules, else `Ok` of the
|
|
@@ -1399,9 +1633,23 @@ function foldArray(results) {
|
|
|
1399
1633
|
*
|
|
1400
1634
|
* @internal
|
|
1401
1635
|
*/
|
|
1402
|
-
function foldRecord(results) {
|
|
1403
|
-
|
|
1404
|
-
|
|
1636
|
+
function foldRecord(results, merge) {
|
|
1637
|
+
let keys;
|
|
1638
|
+
let values;
|
|
1639
|
+
try {
|
|
1640
|
+
[keys, values] = ownEntries(results);
|
|
1641
|
+
} catch (cause) {
|
|
1642
|
+
return defectRes(cause);
|
|
1643
|
+
}
|
|
1644
|
+
return foldArray(values, merge && ((errors) => merge(nameErrors(errors, keys)))).map((values) => Object.fromEntries(keys.map((key, i) => [key, values[i]])));
|
|
1645
|
+
}
|
|
1646
|
+
/** Drop the accumulated indices — the positional forms merge errors alone. @internal */
|
|
1647
|
+
function stripIndices(errors) {
|
|
1648
|
+
return errors.map(([, e]) => e);
|
|
1649
|
+
}
|
|
1650
|
+
/** Pair each accumulated index back onto its key. @internal */
|
|
1651
|
+
function nameErrors(errors, keys) {
|
|
1652
|
+
return errors.map(([i, e]) => [keys[i], e]);
|
|
1405
1653
|
}
|
|
1406
1654
|
/**
|
|
1407
1655
|
* Collect a tuple/array of {@link Result}s into a single `Result` of all their
|
|
@@ -1413,7 +1661,8 @@ function foldRecord(results) {
|
|
|
1413
1661
|
* `Err`. A **fixed tuple** keeps its positional types — `all([Ok(1), Ok("a")])`
|
|
1414
1662
|
* is `Result<[number, string], …>` — while a **dynamic array** `Result<T, E>[]`
|
|
1415
1663
|
* collapses to `Result<T[], E>` with no cast. For a **record** keyed by name,
|
|
1416
|
-
* use {@link allFromDict}.
|
|
1664
|
+
* use {@link allFromDict}. To report **every** `Err` instead of only the first,
|
|
1665
|
+
* use {@link validateAll}.
|
|
1417
1666
|
*
|
|
1418
1667
|
* @category Aggregate
|
|
1419
1668
|
*
|
|
@@ -1436,7 +1685,9 @@ function all(results) {
|
|
|
1436
1685
|
*
|
|
1437
1686
|
* @remarks
|
|
1438
1687
|
* Same folding rules as {@link all}: first `Err` short-circuits, any `Defect`
|
|
1439
|
-
* dominates. This is **not** error accumulation
|
|
1688
|
+
* dominates. This is **not** error accumulation — for that, reach for
|
|
1689
|
+
* {@link validateAllFromDict}, which accumulates every `Err` and folds them into
|
|
1690
|
+
* one modeled error.
|
|
1440
1691
|
*
|
|
1441
1692
|
* @category Aggregate
|
|
1442
1693
|
*
|
|
@@ -1459,7 +1710,8 @@ function allFromDict(results) {
|
|
|
1459
1710
|
* The inputs are resolved **concurrently** (order preserved); the resolved
|
|
1460
1711
|
* `Result`s are then folded with the same rules as {@link all} — first `Err`
|
|
1461
1712
|
* short-circuits, any `Defect` dominates. As ever, the returned `AsyncResult`'s
|
|
1462
|
-
* internal promise never rejects. For a **record**, use {@link allFromDictAsync}
|
|
1713
|
+
* internal promise never rejects. For a **record**, use {@link allFromDictAsync};
|
|
1714
|
+
* to report **every** `Err`, use {@link validateAllAsync}.
|
|
1463
1715
|
*
|
|
1464
1716
|
* @category Aggregate
|
|
1465
1717
|
*
|
|
@@ -1475,7 +1727,7 @@ function allFromDict(results) {
|
|
|
1475
1727
|
* ```
|
|
1476
1728
|
*/
|
|
1477
1729
|
function allAsync(results) {
|
|
1478
|
-
return
|
|
1730
|
+
return settleOrDefect(() => settleAll(results).then((resolved) => foldArray(resolved)));
|
|
1479
1731
|
}
|
|
1480
1732
|
/**
|
|
1481
1733
|
* The asynchronous counterpart of {@link allFromDict}: combine a record of
|
|
@@ -1483,7 +1735,8 @@ function allAsync(results) {
|
|
|
1483
1735
|
*
|
|
1484
1736
|
* @remarks
|
|
1485
1737
|
* Resolved concurrently (order preserved), folded with the {@link all} rules,
|
|
1486
|
-
* and the internal promise never rejects.
|
|
1738
|
+
* and the internal promise never rejects. To report **every** `Err`, use
|
|
1739
|
+
* {@link validateAllFromDictAsync}.
|
|
1487
1740
|
*
|
|
1488
1741
|
* @category Aggregate
|
|
1489
1742
|
*
|
|
@@ -1499,8 +1752,169 @@ function allAsync(results) {
|
|
|
1499
1752
|
* ```
|
|
1500
1753
|
*/
|
|
1501
1754
|
function allFromDictAsync(results) {
|
|
1502
|
-
|
|
1503
|
-
|
|
1755
|
+
return settleOrDefect(() => {
|
|
1756
|
+
const [keys, values] = ownEntries(results);
|
|
1757
|
+
return settleAll(values).then((resolved) => foldRecord(Object.fromEntries(keys.map((key, i) => [key, resolved[i]]))));
|
|
1758
|
+
});
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* Collect a tuple/array of {@link Result}s, **accumulating every** `Err` and
|
|
1762
|
+
* merging them into a single modeled error — the accumulating counterpart of
|
|
1763
|
+
* {@link all}.
|
|
1764
|
+
*
|
|
1765
|
+
* @remarks
|
|
1766
|
+
* Same success channel as {@link all}: a **fixed tuple** keeps its positional
|
|
1767
|
+
* types, a **dynamic array** collapses to `Result<T[], E2>`. The difference is
|
|
1768
|
+
* the error channel — instead of the first `Err` winning, every `Err` is
|
|
1769
|
+
* collected in input order and handed to `merge`, whose return becomes the
|
|
1770
|
+
* modeled error.
|
|
1771
|
+
*
|
|
1772
|
+
* `merge` receives a **non-empty** list, so it is total: it is called only when
|
|
1773
|
+
* at least one `Err` was collected. It is **not** called when every element is
|
|
1774
|
+
* `Ok`, nor when a `Defect` is present.
|
|
1775
|
+
*
|
|
1776
|
+
* Any `Defect` still **dominates** — it wins over the accumulated errors, which
|
|
1777
|
+
* are discarded and never reach `merge`. A defect means something in this batch
|
|
1778
|
+
* failed in a way nobody modeled, so the violations computed alongside it are
|
|
1779
|
+
* not trustworthy. An out-of-contract non-`Result` element becomes a
|
|
1780
|
+
* `TypeError`-caused `Defect` the same way, and a throw inside `merge` becomes
|
|
1781
|
+
* a `Defect` too.
|
|
1782
|
+
*
|
|
1783
|
+
* `merge` must be **synchronous** — an `async` one is a compile error
|
|
1784
|
+
* ({@link NotThenable}), since a `Promise` in `E` is an unqualified rejection.
|
|
1785
|
+
*
|
|
1786
|
+
* For **schema-shaped** input (a request body, a form), reach for
|
|
1787
|
+
* `@unthrown/standard-schema`'s `fromSchema` instead — a validator already
|
|
1788
|
+
* hands you every issue as the modeled error. `validateAll` is for independent
|
|
1789
|
+
* checks you wrote yourself. For a **record** keyed by name, use
|
|
1790
|
+
* {@link validateAllFromDict}.
|
|
1791
|
+
*
|
|
1792
|
+
* @typeParam Rs - the tuple/array of input `Result` types.
|
|
1793
|
+
* @typeParam E2 - the merged error type.
|
|
1794
|
+
* @param results - the results to collect.
|
|
1795
|
+
* @param merge - folds the collected errors into one modeled error.
|
|
1796
|
+
*
|
|
1797
|
+
* @category Aggregate
|
|
1798
|
+
*
|
|
1799
|
+
* @example
|
|
1800
|
+
* ```ts
|
|
1801
|
+
* import { validateAll, Ok, Err } from "unthrown";
|
|
1802
|
+
*
|
|
1803
|
+
* // every Err is collected, not just the first
|
|
1804
|
+
* validateAll([Ok(1), Err("stock"), Err("credit")], (errors) => errors.join(" and "));
|
|
1805
|
+
* // => Err("stock and credit")
|
|
1806
|
+
*
|
|
1807
|
+
* // all-Ok keeps the positional tuple; `merge` never runs
|
|
1808
|
+
* validateAll([Ok(1), Ok("a")], (errors) => errors.join());
|
|
1809
|
+
* // => Ok([1, "a"]) typed Result<[number, string], string>
|
|
1810
|
+
* ```
|
|
1811
|
+
*/
|
|
1812
|
+
function validateAll(results, merge) {
|
|
1813
|
+
return foldArray(results, (errors) => merge(stripIndices(errors)));
|
|
1814
|
+
}
|
|
1815
|
+
/**
|
|
1816
|
+
* Collect a **record** of {@link Result}s, accumulating every `Err` — the
|
|
1817
|
+
* accumulating counterpart of {@link allFromDict}, and the named counterpart of
|
|
1818
|
+
* {@link validateAll}.
|
|
1819
|
+
*
|
|
1820
|
+
* @remarks
|
|
1821
|
+
* `merge` receives a non-empty list of **`[key, error]` entries**, correlated
|
|
1822
|
+
* per key: `{ a: Result<A, E1>; b: Result<B, E2> }` yields
|
|
1823
|
+
* `["a", E1] | ["b", E2]`, so a `switch` on the key narrows the error and an
|
|
1824
|
+
* impossible pairing does not typecheck. That is what keeps two checks sharing
|
|
1825
|
+
* one error type distinguishable. Entries come in key order — `Object.keys`
|
|
1826
|
+
* order, then enumerable symbol keys (a symbol key is folded like any other).
|
|
1827
|
+
*
|
|
1828
|
+
* Every other rule matches {@link validateAll}: any `Defect` dominates and
|
|
1829
|
+
* discards the accumulated errors, a throw in `merge` becomes a `Defect`, and
|
|
1830
|
+
* `merge` must be synchronous.
|
|
1831
|
+
*
|
|
1832
|
+
* @typeParam R - the record of input `Result` types.
|
|
1833
|
+
* @typeParam E2 - the merged error type.
|
|
1834
|
+
* @param results - the results to collect, keyed by name.
|
|
1835
|
+
* @param merge - folds the collected `[key, error]` entries into one error.
|
|
1836
|
+
*
|
|
1837
|
+
* @category Aggregate
|
|
1838
|
+
*
|
|
1839
|
+
* @example
|
|
1840
|
+
* ```ts
|
|
1841
|
+
* import { validateAllFromDict, Ok, Err } from "unthrown";
|
|
1842
|
+
*
|
|
1843
|
+
* validateAllFromDict(
|
|
1844
|
+
* { vatRate: Err("out of range"), currency: Ok("EUR"), dueDate: Err("past") },
|
|
1845
|
+
* (entries) => entries.map(([key, error]) => `${key}: ${error}`).join("; "),
|
|
1846
|
+
* );
|
|
1847
|
+
* // => Err("vatRate: out of range; dueDate: past")
|
|
1848
|
+
* ```
|
|
1849
|
+
*/
|
|
1850
|
+
function validateAllFromDict(results, merge) {
|
|
1851
|
+
return foldRecord(results, (entries) => merge(entries));
|
|
1852
|
+
}
|
|
1853
|
+
/**
|
|
1854
|
+
* The asynchronous counterpart of {@link validateAll}: collect a tuple/array of
|
|
1855
|
+
* {@link AsyncResult}s, accumulating every `Err` into one merged error.
|
|
1856
|
+
*
|
|
1857
|
+
* @remarks
|
|
1858
|
+
* Every {@link validateAll} rule holds, with the inputs resolved
|
|
1859
|
+
* **concurrently** (order preserved) — as with {@link allAsync}, no work is
|
|
1860
|
+
* short-circuited either way; the fail-fast/accumulating split is purely which
|
|
1861
|
+
* errors get reported. The internal promise never rejects: an out-of-contract
|
|
1862
|
+
* rejecting thenable becomes a dominating `Defect`. `merge` stays synchronous
|
|
1863
|
+
* here too — this is exactly where its rejection would land unqualified in `E`.
|
|
1864
|
+
* For a **record**, use {@link validateAllFromDictAsync}.
|
|
1865
|
+
*
|
|
1866
|
+
* @typeParam Rs - the tuple/array of input `AsyncResult` types.
|
|
1867
|
+
* @typeParam E2 - the merged error type.
|
|
1868
|
+
* @param results - the async results to collect.
|
|
1869
|
+
* @param merge - folds the collected errors into one modeled error.
|
|
1870
|
+
*
|
|
1871
|
+
* @category Aggregate
|
|
1872
|
+
*
|
|
1873
|
+
* @example
|
|
1874
|
+
* ```ts
|
|
1875
|
+
* import { validateAllAsync, OkAsync, ErrAsync } from "unthrown";
|
|
1876
|
+
*
|
|
1877
|
+
* const checked = validateAllAsync(
|
|
1878
|
+
* [OkAsync(1), ErrAsync("stock"), ErrAsync("credit")],
|
|
1879
|
+
* (errors) => errors.join(" and "),
|
|
1880
|
+
* );
|
|
1881
|
+
* // (await checked) => Err("stock and credit")
|
|
1882
|
+
* ```
|
|
1883
|
+
*/
|
|
1884
|
+
function validateAllAsync(results, merge) {
|
|
1885
|
+
return settleOrDefect(() => settleAll(results).then((resolved) => foldArray(resolved, (errors) => merge(stripIndices(errors)))));
|
|
1886
|
+
}
|
|
1887
|
+
/**
|
|
1888
|
+
* The asynchronous counterpart of {@link validateAllFromDict}: collect a record
|
|
1889
|
+
* of {@link AsyncResult}s, accumulating every `Err` into one merged error.
|
|
1890
|
+
*
|
|
1891
|
+
* @remarks
|
|
1892
|
+
* The {@link validateAllFromDict} rules, over inputs resolved concurrently as
|
|
1893
|
+
* in {@link validateAllAsync}.
|
|
1894
|
+
*
|
|
1895
|
+
* @typeParam R - the record of input `AsyncResult` types.
|
|
1896
|
+
* @typeParam E2 - the merged error type.
|
|
1897
|
+
* @param results - the async results to collect, keyed by name.
|
|
1898
|
+
* @param merge - folds the collected `[key, error]` entries into one error.
|
|
1899
|
+
*
|
|
1900
|
+
* @category Aggregate
|
|
1901
|
+
*
|
|
1902
|
+
* @example
|
|
1903
|
+
* ```ts
|
|
1904
|
+
* import { validateAllFromDictAsync, OkAsync, ErrAsync } from "unthrown";
|
|
1905
|
+
*
|
|
1906
|
+
* const checked = validateAllFromDictAsync(
|
|
1907
|
+
* { stock: ErrAsync("none left"), credit: OkAsync(500) },
|
|
1908
|
+
* (entries) => entries.map(([key, error]) => `${key}: ${error}`).join("; "),
|
|
1909
|
+
* );
|
|
1910
|
+
* // (await checked) => Err("stock: none left")
|
|
1911
|
+
* ```
|
|
1912
|
+
*/
|
|
1913
|
+
function validateAllFromDictAsync(results, merge) {
|
|
1914
|
+
return settleOrDefect(() => {
|
|
1915
|
+
const [keys, values] = ownEntries(results);
|
|
1916
|
+
return settleAll(values).then((resolved) => foldRecord(Object.fromEntries(keys.map((key, i) => [key, resolved[i]])), (entries) => merge(entries)));
|
|
1917
|
+
});
|
|
1504
1918
|
}
|
|
1505
1919
|
//#endregion
|
|
1506
1920
|
//#region src/facade.ts
|
|
@@ -1509,7 +1923,8 @@ function allFromDictAsync(results) {
|
|
|
1509
1923
|
* single, discoverable namespace: {@link Result.Ok}, {@link Result.Err},
|
|
1510
1924
|
* {@link Result.Do}, {@link Result.fromNullable}, {@link Result.fromThrowable},
|
|
1511
1925
|
* {@link Result.fromSafeThrowable}, {@link Result.all},
|
|
1512
|
-
* {@link Result.allFromDict}, {@link Result.
|
|
1926
|
+
* {@link Result.allFromDict}, {@link Result.validateAll},
|
|
1927
|
+
* {@link Result.validateAllFromDict}, {@link Result.isOk}, {@link Result.isErr},
|
|
1513
1928
|
* {@link Result.isDefect}, {@link Result.isResult}.
|
|
1514
1929
|
*
|
|
1515
1930
|
* @remarks
|
|
@@ -1539,6 +1954,8 @@ const Result = {
|
|
|
1539
1954
|
fromSafeThrowable,
|
|
1540
1955
|
all,
|
|
1541
1956
|
allFromDict,
|
|
1957
|
+
validateAll,
|
|
1958
|
+
validateAllFromDict,
|
|
1542
1959
|
isOk,
|
|
1543
1960
|
isErr,
|
|
1544
1961
|
isDefect,
|
|
@@ -1549,7 +1966,8 @@ const Result = {
|
|
|
1549
1966
|
* the matching namespace: {@link AsyncResult.Ok}, {@link AsyncResult.Err},
|
|
1550
1967
|
* {@link AsyncResult.Do}, {@link AsyncResult.fromExecutor},
|
|
1551
1968
|
* {@link AsyncResult.fromPromise}, {@link AsyncResult.fromSafePromise},
|
|
1552
|
-
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict}
|
|
1969
|
+
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict},
|
|
1970
|
+
* {@link AsyncResult.validateAll}, {@link AsyncResult.validateAllFromDict}.
|
|
1553
1971
|
*
|
|
1554
1972
|
* @remarks
|
|
1555
1973
|
* The async sibling of {@link Result}. Statics are grouped by what they
|
|
@@ -1560,7 +1978,8 @@ const Result = {
|
|
|
1560
1978
|
* functions carry (`AsyncResult.Ok` is `OkAsync`; `AsyncResult.Err` is
|
|
1561
1979
|
* `ErrAsync`; `AsyncResult.Do` is `DoAsync`; `AsyncResult.all` is `allAsync`;
|
|
1562
1980
|
* `AsyncResult.allFromDict` is
|
|
1563
|
-
* `allFromDictAsync`). Like
|
|
1981
|
+
* `allFromDictAsync`; `AsyncResult.validateAll` is `validateAllAsync`). Like
|
|
1982
|
+
* {@link Result}, the free functions remain the
|
|
1564
1983
|
* primary, tree-shakeable API; the value `AsyncResult` and the type
|
|
1565
1984
|
* {@link AsyncResult} share one name.
|
|
1566
1985
|
*
|
|
@@ -1584,7 +2003,9 @@ const AsyncResult = {
|
|
|
1584
2003
|
fromPromise,
|
|
1585
2004
|
fromSafePromise,
|
|
1586
2005
|
all: allAsync,
|
|
1587
|
-
allFromDict: allFromDictAsync
|
|
2006
|
+
allFromDict: allFromDictAsync,
|
|
2007
|
+
validateAll: validateAllAsync,
|
|
2008
|
+
validateAllFromDict: validateAllFromDictAsync
|
|
1588
2009
|
};
|
|
1589
2010
|
//#endregion
|
|
1590
2011
|
//#region src/tagged.ts
|
|
@@ -1704,3 +2125,7 @@ exports.isErr = isErr;
|
|
|
1704
2125
|
exports.isOk = isOk;
|
|
1705
2126
|
exports.isResult = isResult;
|
|
1706
2127
|
exports.match = match;
|
|
2128
|
+
exports.validateAll = validateAll;
|
|
2129
|
+
exports.validateAllAsync = validateAllAsync;
|
|
2130
|
+
exports.validateAllFromDict = validateAllFromDict;
|
|
2131
|
+
exports.validateAllFromDictAsync = validateAllFromDictAsync;
|