unthrown 5.3.0 → 5.5.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/dist/index.cjs +77 -138
- package/dist/index.d.cts +8 -11
- package/dist/index.d.mts +8 -11
- package/dist/index.mjs +77 -138
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -135,7 +135,7 @@ const universal = pattern(() => true);
|
|
|
135
135
|
/**
|
|
136
136
|
* The pattern namespace (unthrown's own; the former ts-pattern `P`):
|
|
137
137
|
*
|
|
138
|
-
* - `P._`
|
|
138
|
+
* - `P._` — the universal catch-all, and an **escape hatch** rather
|
|
139
139
|
* than the default: matching the error channel means naming its cases, so
|
|
140
140
|
* reach for this only where they cannot be named. Matches anything, and
|
|
141
141
|
* (because its phantom type is `unknown`) makes the builder provably
|
|
@@ -153,25 +153,22 @@ const universal = pattern(() => true);
|
|
|
153
153
|
* branch's parameter to that variant, payload included. The workhorse of the
|
|
154
154
|
* error channel: `matcher.with(P.tag("NotFound"), (e) => …)`. It composes like
|
|
155
155
|
* any other pattern — in a grouped arm
|
|
156
|
-
* (`.with(P.tag("A"), P.tag("B"), handler)`)
|
|
156
|
+
* (`.with(P.tag("A"), P.tag("B"), handler)`).
|
|
157
157
|
* - `P.instanceOf(Cls)` — an `instanceof` check, narrowing to the class
|
|
158
158
|
* instance type (for union members that are not tagged, e.g. a third-party
|
|
159
159
|
* error class).
|
|
160
|
-
* - `P.when(guard)` — an arbitrary type-guard predicate.
|
|
161
|
-
*
|
|
162
|
-
*
|
|
160
|
+
* - `P.when(guard)` — an arbitrary type-guard predicate. Also the way to match
|
|
161
|
+
* a primitive shape (`P.when((v): v is string => typeof v === "string")`),
|
|
162
|
+
* and grouping patterns under one handler is what a `.with(a, b, handler)`
|
|
163
|
+
* arm already does.
|
|
163
164
|
*
|
|
164
165
|
* @category Constructors
|
|
165
166
|
*/
|
|
166
167
|
const P = Object.freeze({
|
|
167
168
|
_: universal,
|
|
168
|
-
any: universal,
|
|
169
169
|
tag: (value) => ({ _tag: value }),
|
|
170
170
|
instanceOf: (cls) => pattern((value) => value instanceof cls),
|
|
171
|
-
when: (guard) => pattern(guard)
|
|
172
|
-
union: (...patterns) => pattern((value) => patterns.some((sub) => matches(sub, value))),
|
|
173
|
-
string: pattern((value) => typeof value === "string"),
|
|
174
|
-
number: pattern((value) => typeof value === "number")
|
|
171
|
+
when: (guard) => pattern(guard)
|
|
175
172
|
});
|
|
176
173
|
//#endregion
|
|
177
174
|
//#region src/defect.ts
|
|
@@ -569,6 +566,30 @@ function passThrough(self) {
|
|
|
569
566
|
return self;
|
|
570
567
|
}
|
|
571
568
|
/**
|
|
569
|
+
* Runtime thenable probe, shared by the combinator-side net below and the
|
|
570
|
+
* boundary nets in `interop.ts`.
|
|
571
|
+
*
|
|
572
|
+
* @remarks
|
|
573
|
+
* Reading `.then` can itself **throw** — a hostile getter, or a Proxy `get`
|
|
574
|
+
* trap — so this is deliberately not a total function, and every caller must
|
|
575
|
+
* account for that. Two shapes are sanctioned, and there is no third:
|
|
576
|
+
*
|
|
577
|
+
* - inside a `try` that routes the throw to a `Defect` (the boundaries in
|
|
578
|
+
* `interop.ts`, where a hostile value arriving at a triage point *is* an
|
|
579
|
+
* unmodeled failure and should surface as one); or
|
|
580
|
+
* - through {@link silenceIfThenable}, for a value being **discarded**, where
|
|
581
|
+
* there is no Defect channel to route to and the only correct answer is to
|
|
582
|
+
* drop it without throwing.
|
|
583
|
+
*
|
|
584
|
+
* Calling it bare, outside both, is a bug: the throw escapes into whatever
|
|
585
|
+
* context invoked it.
|
|
586
|
+
*
|
|
587
|
+
* @internal
|
|
588
|
+
*/
|
|
589
|
+
function isThenable(x) {
|
|
590
|
+
return (typeof x === "object" || typeof x === "function") && x !== null && typeof x.then === "function";
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
572
593
|
* Adopt-and-silence a thenable a combinator is about to **discard**.
|
|
573
594
|
*
|
|
574
595
|
* @remarks
|
|
@@ -589,7 +610,7 @@ function passThrough(self) {
|
|
|
589
610
|
*/
|
|
590
611
|
function silenceIfThenable(value) {
|
|
591
612
|
try {
|
|
592
|
-
if ((
|
|
613
|
+
if (isThenable(value)) Promise.resolve(value).then(void 0, () => void 0);
|
|
593
614
|
} catch {}
|
|
594
615
|
}
|
|
595
616
|
/**
|
|
@@ -670,18 +691,29 @@ var AsyncRes = class AsyncRes {
|
|
|
670
691
|
constructor(promise) {
|
|
671
692
|
this.#promise = promise;
|
|
672
693
|
}
|
|
694
|
+
/**
|
|
695
|
+
* Lift a **synchronous** `Result` combinator over the wrapped promise.
|
|
696
|
+
*
|
|
697
|
+
* @remarks
|
|
698
|
+
* Every method below that does no `await` of its own is exactly its `Res`
|
|
699
|
+
* twin applied to the settled `Result` — same tag check, same try/catch, same
|
|
700
|
+
* throw→defect net. Delegating instead of restating it keeps the two surfaces
|
|
701
|
+
* from drifting: a fix to the sync combinator is the fix to the async one.
|
|
702
|
+
* The methods that DO await a callback's result (`flatMap`, `flatTap`,
|
|
703
|
+
* `bind`, `flatMapErrCases`, `flatTapErrCases`, `recoverDefect` — the ones
|
|
704
|
+
* whose callback may hand back an `AsyncResult`) genuinely differ and are
|
|
705
|
+
* still written out in full.
|
|
706
|
+
*
|
|
707
|
+
* @internal
|
|
708
|
+
*/
|
|
709
|
+
#lift(f) {
|
|
710
|
+
return new AsyncRes(this.#promise.then(f));
|
|
711
|
+
}
|
|
673
712
|
then(onfulfilled, onrejected) {
|
|
674
713
|
return this.#promise.then(onfulfilled, onrejected);
|
|
675
714
|
}
|
|
676
715
|
map(f) {
|
|
677
|
-
return
|
|
678
|
-
if (r.tag !== "Ok") return passThrough(r);
|
|
679
|
-
try {
|
|
680
|
-
return okRes(f(r.value));
|
|
681
|
-
} catch (cause) {
|
|
682
|
-
return defectRes(cause);
|
|
683
|
-
}
|
|
684
|
-
}));
|
|
716
|
+
return this.#lift((r) => r.map(f));
|
|
685
717
|
}
|
|
686
718
|
flatMap(f) {
|
|
687
719
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -695,15 +727,7 @@ var AsyncRes = class AsyncRes {
|
|
|
695
727
|
}));
|
|
696
728
|
}
|
|
697
729
|
tap(f) {
|
|
698
|
-
return
|
|
699
|
-
if (r.tag !== "Ok") return r;
|
|
700
|
-
try {
|
|
701
|
-
silenceIfThenable(f(r.value));
|
|
702
|
-
return r;
|
|
703
|
-
} catch (cause) {
|
|
704
|
-
return defectRes(cause);
|
|
705
|
-
}
|
|
706
|
-
}));
|
|
730
|
+
return this.#lift((r) => r.tap(f));
|
|
707
731
|
}
|
|
708
732
|
flatTap(f) {
|
|
709
733
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -734,45 +758,19 @@ var AsyncRes = class AsyncRes {
|
|
|
734
758
|
}));
|
|
735
759
|
}
|
|
736
760
|
let(name, f) {
|
|
737
|
-
return
|
|
738
|
-
if (r.tag !== "Ok") return passThrough(r);
|
|
739
|
-
try {
|
|
740
|
-
return okRes({
|
|
741
|
-
...scopeOf(r.value),
|
|
742
|
-
[name]: f(r.value)
|
|
743
|
-
});
|
|
744
|
-
} catch (cause) {
|
|
745
|
-
return defectRes(cause);
|
|
746
|
-
}
|
|
747
|
-
}));
|
|
761
|
+
return this.#lift((r) => r.let(name, f));
|
|
748
762
|
}
|
|
749
763
|
as(value) {
|
|
750
|
-
return
|
|
764
|
+
return this.#lift((r) => r.as(value));
|
|
751
765
|
}
|
|
752
766
|
discard() {
|
|
753
|
-
return
|
|
767
|
+
return this.#lift((r) => r.discard());
|
|
754
768
|
}
|
|
755
769
|
ensure(predicate, onFail) {
|
|
756
|
-
return
|
|
757
|
-
if (r.tag !== "Ok") return passThrough(r);
|
|
758
|
-
try {
|
|
759
|
-
return predicate(r.value) ? r : errRes(onFail(r.value));
|
|
760
|
-
} catch (cause) {
|
|
761
|
-
return defectRes(cause);
|
|
762
|
-
}
|
|
763
|
-
}));
|
|
770
|
+
return this.#lift((r) => r.ensure(predicate, onFail));
|
|
764
771
|
}
|
|
765
772
|
mapErrCases(f) {
|
|
766
|
-
return
|
|
767
|
-
if (r.tag !== "Err") return passThrough(r);
|
|
768
|
-
try {
|
|
769
|
-
const out = runMatch(f, r.error);
|
|
770
|
-
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
771
|
-
return errRes(out);
|
|
772
|
-
} catch (cause) {
|
|
773
|
-
return defectRes(cause);
|
|
774
|
-
}
|
|
775
|
-
}));
|
|
773
|
+
return this.#lift((r) => r.mapErrCases(f));
|
|
776
774
|
}
|
|
777
775
|
flatMapErrCases(f) {
|
|
778
776
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -789,29 +787,10 @@ var AsyncRes = class AsyncRes {
|
|
|
789
787
|
}));
|
|
790
788
|
}
|
|
791
789
|
recoverErrCases(f) {
|
|
792
|
-
return
|
|
793
|
-
if (r.tag !== "Err") return passThrough(r);
|
|
794
|
-
try {
|
|
795
|
-
const out = runMatch(f, r.error);
|
|
796
|
-
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
797
|
-
return okRes(out);
|
|
798
|
-
} catch (cause) {
|
|
799
|
-
return defectRes(cause);
|
|
800
|
-
}
|
|
801
|
-
}));
|
|
790
|
+
return this.#lift((r) => r.recoverErrCases(f));
|
|
802
791
|
}
|
|
803
792
|
tapErrCases(f) {
|
|
804
|
-
return
|
|
805
|
-
if (r.tag !== "Err") return r;
|
|
806
|
-
try {
|
|
807
|
-
const out = runMatch(f, r.error);
|
|
808
|
-
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
809
|
-
silenceIfThenable(out);
|
|
810
|
-
return r;
|
|
811
|
-
} catch (cause) {
|
|
812
|
-
return observerThrowToDefect(cause, r.error);
|
|
813
|
-
}
|
|
814
|
-
}));
|
|
793
|
+
return this.#lift((r) => r.tapErrCases(f));
|
|
815
794
|
}
|
|
816
795
|
flatTapErrCases(f) {
|
|
817
796
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -839,26 +818,10 @@ var AsyncRes = class AsyncRes {
|
|
|
839
818
|
}));
|
|
840
819
|
}
|
|
841
820
|
tapDefect(f) {
|
|
842
|
-
return
|
|
843
|
-
if (r.tag !== "Defect") return r;
|
|
844
|
-
try {
|
|
845
|
-
silenceIfThenable(f(r.cause));
|
|
846
|
-
return r;
|
|
847
|
-
} catch (cause) {
|
|
848
|
-
return observerThrowToDefect(cause, r.cause);
|
|
849
|
-
}
|
|
850
|
-
}));
|
|
821
|
+
return this.#lift((r) => r.tapDefect(f));
|
|
851
822
|
}
|
|
852
823
|
tapFailure(f) {
|
|
853
|
-
return
|
|
854
|
-
if (r.tag === "Ok") return r;
|
|
855
|
-
try {
|
|
856
|
-
silenceIfThenable(f(r));
|
|
857
|
-
return r;
|
|
858
|
-
} catch (cause) {
|
|
859
|
-
return observerThrowToDefect(cause, r.tag === "Err" ? r.error : r.cause);
|
|
860
|
-
}
|
|
861
|
-
}));
|
|
824
|
+
return this.#lift((r) => r.tapFailure(f));
|
|
862
825
|
}
|
|
863
826
|
match(cases) {
|
|
864
827
|
return this.#promise.then((r) => r.match(cases));
|
|
@@ -1340,7 +1303,7 @@ function fromExecutor(executor) {
|
|
|
1340
1303
|
return;
|
|
1341
1304
|
}
|
|
1342
1305
|
if (!isResult(result)) {
|
|
1343
|
-
|
|
1306
|
+
silenceIfThenable(result);
|
|
1344
1307
|
resolve(defectRes(/* @__PURE__ */ new TypeError("unthrown: fromExecutor's settle received a non-Result value")));
|
|
1345
1308
|
return;
|
|
1346
1309
|
}
|
|
@@ -1394,15 +1357,6 @@ function thenableReturnDefect(value) {
|
|
|
1394
1357
|
return defectRes(/* @__PURE__ */ new TypeError("unthrown: fromThrowable/fromSafeThrowable wrap a SYNCHRONOUS function, but `fn` returned a thenable — its rejection would escape qualification. Use fromPromise/fromSafePromise instead."));
|
|
1395
1358
|
}
|
|
1396
1359
|
/**
|
|
1397
|
-
* Runtime thenable probe for the belt-and-braces guards above. Called inside the
|
|
1398
|
-
* caller's `try`, so even a hostile `.then` getter lands on the Defect path.
|
|
1399
|
-
*
|
|
1400
|
-
* @internal
|
|
1401
|
-
*/
|
|
1402
|
-
function isThenable(x) {
|
|
1403
|
-
return (typeof x === "object" || typeof x === "function") && x !== null && typeof x.then === "function";
|
|
1404
|
-
}
|
|
1405
|
-
/**
|
|
1406
1360
|
* Fold an array of settled `Result`s: first `Err` wins, any `Defect` dominates,
|
|
1407
1361
|
* else `Ok` of the values array.
|
|
1408
1362
|
*
|
|
@@ -1431,32 +1385,23 @@ function foldArray(results) {
|
|
|
1431
1385
|
}
|
|
1432
1386
|
/**
|
|
1433
1387
|
* Fold a record of settled `Result`s with the same rules, else `Ok` of the
|
|
1434
|
-
* record of values.
|
|
1435
|
-
*
|
|
1388
|
+
* record of values.
|
|
1389
|
+
*
|
|
1390
|
+
* @remarks
|
|
1391
|
+
* The positional fold already implements every rule (first `Err` wins, any
|
|
1392
|
+
* `Defect` dominates, a non-`Result` element becomes a `Defect`), so this pairs
|
|
1393
|
+
* the keys back onto its success value rather than restating them.
|
|
1394
|
+
*
|
|
1395
|
+
* `Object.fromEntries` is what makes a caller-supplied `"__proto__"` key safe:
|
|
1396
|
+
* it builds each key with CreateDataProperty, which defines an own property
|
|
1397
|
+
* instead of invoking the `__proto__` setter — the same guarantee the previous
|
|
1398
|
+
* explicit `Object.defineProperty` loop bought by hand.
|
|
1436
1399
|
*
|
|
1437
1400
|
* @internal
|
|
1438
1401
|
*/
|
|
1439
1402
|
function foldRecord(results) {
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
const values = {};
|
|
1443
|
-
for (const [key, r] of Object.entries(results)) {
|
|
1444
|
-
if (!isResult(r)) {
|
|
1445
|
-
firstDefect ??= nonResultDefect();
|
|
1446
|
-
break;
|
|
1447
|
-
}
|
|
1448
|
-
if (r.tag === "Defect") {
|
|
1449
|
-
firstDefect ??= r;
|
|
1450
|
-
break;
|
|
1451
|
-
} else if (r.tag === "Err") firstErr ??= r;
|
|
1452
|
-
else Object.defineProperty(values, key, {
|
|
1453
|
-
value: r.value,
|
|
1454
|
-
enumerable: true,
|
|
1455
|
-
writable: true,
|
|
1456
|
-
configurable: true
|
|
1457
|
-
});
|
|
1458
|
-
}
|
|
1459
|
-
return firstDefect ?? firstErr ?? Ok(values);
|
|
1403
|
+
const keys = Object.keys(results);
|
|
1404
|
+
return foldArray(Object.values(results)).map((values) => Object.fromEntries(keys.map((key, i) => [key, values[i]])));
|
|
1460
1405
|
}
|
|
1461
1406
|
/**
|
|
1462
1407
|
* Collect a tuple/array of {@link Result}s into a single `Result` of all their
|
|
@@ -1554,14 +1499,8 @@ function allAsync(results) {
|
|
|
1554
1499
|
* ```
|
|
1555
1500
|
*/
|
|
1556
1501
|
function allFromDictAsync(results) {
|
|
1557
|
-
const
|
|
1558
|
-
return new AsyncRes(Promise.all(
|
|
1559
|
-
const byKey = Object.create(null);
|
|
1560
|
-
entries.forEach(([key], i) => {
|
|
1561
|
-
byKey[key] = resolved[i];
|
|
1562
|
-
});
|
|
1563
|
-
return foldRecord(byKey);
|
|
1564
|
-
}));
|
|
1502
|
+
const keys = Object.keys(results);
|
|
1503
|
+
return new AsyncRes(Promise.all(Object.values(results).map((ar) => Promise.resolve(ar).then((x) => x, (cause) => defectRes(cause)))).then((resolved) => foldArray(resolved).map((values) => Object.fromEntries(keys.map((key, i) => [key, values[i]])))));
|
|
1565
1504
|
}
|
|
1566
1505
|
//#endregion
|
|
1567
1506
|
//#region src/facade.ts
|
package/dist/index.d.cts
CHANGED
|
@@ -44,7 +44,7 @@ type PatternMatcher<M> = {
|
|
|
44
44
|
readonly [MATCHES]?: M;
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
|
-
* The statically-known universal pattern — the type of `P._`
|
|
47
|
+
* The statically-known universal pattern — the type of `P._` only.
|
|
48
48
|
* The phantom `UNIVERSAL` marker is *required*, so no other
|
|
49
49
|
* `PatternMatcher<unknown>` (e.g. a `P.when` guard that happens to be
|
|
50
50
|
* universal) is assignable: the catch-all `.with` overload must only fire for
|
|
@@ -127,7 +127,7 @@ type PinTooLate = {
|
|
|
127
127
|
*/
|
|
128
128
|
type Matcher<E, Remaining, O, Declared = Unset> = {
|
|
129
129
|
/**
|
|
130
|
-
* The catch-all arm: `.with(P._, handler)`
|
|
130
|
+
* The catch-all arm: `.with(P._, handler)` — the
|
|
131
131
|
* wildcard **escape hatch**, not the way to handle a concrete error union
|
|
132
132
|
* (name those cases; `@unthrown/oxlint`'s `no-catch-all-pattern`, in its
|
|
133
133
|
* `recommended` preset, flags the wildcard).
|
|
@@ -225,7 +225,7 @@ declare function match<const E>(value: E): Matcher<E, E, never>;
|
|
|
225
225
|
/**
|
|
226
226
|
* The pattern namespace (unthrown's own; the former ts-pattern `P`):
|
|
227
227
|
*
|
|
228
|
-
* - `P._`
|
|
228
|
+
* - `P._` — the universal catch-all, and an **escape hatch** rather
|
|
229
229
|
* than the default: matching the error channel means naming its cases, so
|
|
230
230
|
* reach for this only where they cannot be named. Matches anything, and
|
|
231
231
|
* (because its phantom type is `unknown`) makes the builder provably
|
|
@@ -243,27 +243,24 @@ declare function match<const E>(value: E): Matcher<E, E, never>;
|
|
|
243
243
|
* branch's parameter to that variant, payload included. The workhorse of the
|
|
244
244
|
* error channel: `matcher.with(P.tag("NotFound"), (e) => …)`. It composes like
|
|
245
245
|
* any other pattern — in a grouped arm
|
|
246
|
-
* (`.with(P.tag("A"), P.tag("B"), handler)`)
|
|
246
|
+
* (`.with(P.tag("A"), P.tag("B"), handler)`).
|
|
247
247
|
* - `P.instanceOf(Cls)` — an `instanceof` check, narrowing to the class
|
|
248
248
|
* instance type (for union members that are not tagged, e.g. a third-party
|
|
249
249
|
* error class).
|
|
250
|
-
* - `P.when(guard)` — an arbitrary type-guard predicate.
|
|
251
|
-
*
|
|
252
|
-
*
|
|
250
|
+
* - `P.when(guard)` — an arbitrary type-guard predicate. Also the way to match
|
|
251
|
+
* a primitive shape (`P.when((v): v is string => typeof v === "string")`),
|
|
252
|
+
* and grouping patterns under one handler is what a `.with(a, b, handler)`
|
|
253
|
+
* arm already does.
|
|
253
254
|
*
|
|
254
255
|
* @category Constructors
|
|
255
256
|
*/
|
|
256
257
|
declare const P: Readonly<{
|
|
257
258
|
_: UniversalPattern;
|
|
258
|
-
any: UniversalPattern;
|
|
259
259
|
tag: <const Tag extends string>(value: Tag) => {
|
|
260
260
|
_tag: Tag;
|
|
261
261
|
};
|
|
262
262
|
instanceOf: <C extends abstract new (...args: never[]) => unknown>(cls: C) => PatternMatcher<InstanceType<C>>;
|
|
263
263
|
when: <G>(guard: (value: unknown) => value is G) => PatternMatcher<G>;
|
|
264
|
-
union: <const Pts extends readonly [unknown, ...unknown[]]>(...patterns: Pts) => PatternMatcher<MatchedOf<Pts[number]>>;
|
|
265
|
-
string: PatternMatcher<string>;
|
|
266
|
-
number: PatternMatcher<number>;
|
|
267
264
|
}>;
|
|
268
265
|
//#endregion
|
|
269
266
|
//#region src/types.d.ts
|
package/dist/index.d.mts
CHANGED
|
@@ -44,7 +44,7 @@ type PatternMatcher<M> = {
|
|
|
44
44
|
readonly [MATCHES]?: M;
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
|
-
* The statically-known universal pattern — the type of `P._`
|
|
47
|
+
* The statically-known universal pattern — the type of `P._` only.
|
|
48
48
|
* The phantom `UNIVERSAL` marker is *required*, so no other
|
|
49
49
|
* `PatternMatcher<unknown>` (e.g. a `P.when` guard that happens to be
|
|
50
50
|
* universal) is assignable: the catch-all `.with` overload must only fire for
|
|
@@ -127,7 +127,7 @@ type PinTooLate = {
|
|
|
127
127
|
*/
|
|
128
128
|
type Matcher<E, Remaining, O, Declared = Unset> = {
|
|
129
129
|
/**
|
|
130
|
-
* The catch-all arm: `.with(P._, handler)`
|
|
130
|
+
* The catch-all arm: `.with(P._, handler)` — the
|
|
131
131
|
* wildcard **escape hatch**, not the way to handle a concrete error union
|
|
132
132
|
* (name those cases; `@unthrown/oxlint`'s `no-catch-all-pattern`, in its
|
|
133
133
|
* `recommended` preset, flags the wildcard).
|
|
@@ -225,7 +225,7 @@ declare function match<const E>(value: E): Matcher<E, E, never>;
|
|
|
225
225
|
/**
|
|
226
226
|
* The pattern namespace (unthrown's own; the former ts-pattern `P`):
|
|
227
227
|
*
|
|
228
|
-
* - `P._`
|
|
228
|
+
* - `P._` — the universal catch-all, and an **escape hatch** rather
|
|
229
229
|
* than the default: matching the error channel means naming its cases, so
|
|
230
230
|
* reach for this only where they cannot be named. Matches anything, and
|
|
231
231
|
* (because its phantom type is `unknown`) makes the builder provably
|
|
@@ -243,27 +243,24 @@ declare function match<const E>(value: E): Matcher<E, E, never>;
|
|
|
243
243
|
* branch's parameter to that variant, payload included. The workhorse of the
|
|
244
244
|
* error channel: `matcher.with(P.tag("NotFound"), (e) => …)`. It composes like
|
|
245
245
|
* any other pattern — in a grouped arm
|
|
246
|
-
* (`.with(P.tag("A"), P.tag("B"), handler)`)
|
|
246
|
+
* (`.with(P.tag("A"), P.tag("B"), handler)`).
|
|
247
247
|
* - `P.instanceOf(Cls)` — an `instanceof` check, narrowing to the class
|
|
248
248
|
* instance type (for union members that are not tagged, e.g. a third-party
|
|
249
249
|
* error class).
|
|
250
|
-
* - `P.when(guard)` — an arbitrary type-guard predicate.
|
|
251
|
-
*
|
|
252
|
-
*
|
|
250
|
+
* - `P.when(guard)` — an arbitrary type-guard predicate. Also the way to match
|
|
251
|
+
* a primitive shape (`P.when((v): v is string => typeof v === "string")`),
|
|
252
|
+
* and grouping patterns under one handler is what a `.with(a, b, handler)`
|
|
253
|
+
* arm already does.
|
|
253
254
|
*
|
|
254
255
|
* @category Constructors
|
|
255
256
|
*/
|
|
256
257
|
declare const P: Readonly<{
|
|
257
258
|
_: UniversalPattern;
|
|
258
|
-
any: UniversalPattern;
|
|
259
259
|
tag: <const Tag extends string>(value: Tag) => {
|
|
260
260
|
_tag: Tag;
|
|
261
261
|
};
|
|
262
262
|
instanceOf: <C extends abstract new (...args: never[]) => unknown>(cls: C) => PatternMatcher<InstanceType<C>>;
|
|
263
263
|
when: <G>(guard: (value: unknown) => value is G) => PatternMatcher<G>;
|
|
264
|
-
union: <const Pts extends readonly [unknown, ...unknown[]]>(...patterns: Pts) => PatternMatcher<MatchedOf<Pts[number]>>;
|
|
265
|
-
string: PatternMatcher<string>;
|
|
266
|
-
number: PatternMatcher<number>;
|
|
267
264
|
}>;
|
|
268
265
|
//#endregion
|
|
269
266
|
//#region src/types.d.ts
|
package/dist/index.mjs
CHANGED
|
@@ -134,7 +134,7 @@ const universal = pattern(() => true);
|
|
|
134
134
|
/**
|
|
135
135
|
* The pattern namespace (unthrown's own; the former ts-pattern `P`):
|
|
136
136
|
*
|
|
137
|
-
* - `P._`
|
|
137
|
+
* - `P._` — the universal catch-all, and an **escape hatch** rather
|
|
138
138
|
* than the default: matching the error channel means naming its cases, so
|
|
139
139
|
* reach for this only where they cannot be named. Matches anything, and
|
|
140
140
|
* (because its phantom type is `unknown`) makes the builder provably
|
|
@@ -152,25 +152,22 @@ const universal = pattern(() => true);
|
|
|
152
152
|
* branch's parameter to that variant, payload included. The workhorse of the
|
|
153
153
|
* error channel: `matcher.with(P.tag("NotFound"), (e) => …)`. It composes like
|
|
154
154
|
* any other pattern — in a grouped arm
|
|
155
|
-
* (`.with(P.tag("A"), P.tag("B"), handler)`)
|
|
155
|
+
* (`.with(P.tag("A"), P.tag("B"), handler)`).
|
|
156
156
|
* - `P.instanceOf(Cls)` — an `instanceof` check, narrowing to the class
|
|
157
157
|
* instance type (for union members that are not tagged, e.g. a third-party
|
|
158
158
|
* error class).
|
|
159
|
-
* - `P.when(guard)` — an arbitrary type-guard predicate.
|
|
160
|
-
*
|
|
161
|
-
*
|
|
159
|
+
* - `P.when(guard)` — an arbitrary type-guard predicate. Also the way to match
|
|
160
|
+
* a primitive shape (`P.when((v): v is string => typeof v === "string")`),
|
|
161
|
+
* and grouping patterns under one handler is what a `.with(a, b, handler)`
|
|
162
|
+
* arm already does.
|
|
162
163
|
*
|
|
163
164
|
* @category Constructors
|
|
164
165
|
*/
|
|
165
166
|
const P = Object.freeze({
|
|
166
167
|
_: universal,
|
|
167
|
-
any: universal,
|
|
168
168
|
tag: (value) => ({ _tag: value }),
|
|
169
169
|
instanceOf: (cls) => pattern((value) => value instanceof cls),
|
|
170
|
-
when: (guard) => pattern(guard)
|
|
171
|
-
union: (...patterns) => pattern((value) => patterns.some((sub) => matches(sub, value))),
|
|
172
|
-
string: pattern((value) => typeof value === "string"),
|
|
173
|
-
number: pattern((value) => typeof value === "number")
|
|
170
|
+
when: (guard) => pattern(guard)
|
|
174
171
|
});
|
|
175
172
|
//#endregion
|
|
176
173
|
//#region src/defect.ts
|
|
@@ -568,6 +565,30 @@ function passThrough(self) {
|
|
|
568
565
|
return self;
|
|
569
566
|
}
|
|
570
567
|
/**
|
|
568
|
+
* Runtime thenable probe, shared by the combinator-side net below and the
|
|
569
|
+
* boundary nets in `interop.ts`.
|
|
570
|
+
*
|
|
571
|
+
* @remarks
|
|
572
|
+
* Reading `.then` can itself **throw** — a hostile getter, or a Proxy `get`
|
|
573
|
+
* trap — so this is deliberately not a total function, and every caller must
|
|
574
|
+
* account for that. Two shapes are sanctioned, and there is no third:
|
|
575
|
+
*
|
|
576
|
+
* - inside a `try` that routes the throw to a `Defect` (the boundaries in
|
|
577
|
+
* `interop.ts`, where a hostile value arriving at a triage point *is* an
|
|
578
|
+
* unmodeled failure and should surface as one); or
|
|
579
|
+
* - through {@link silenceIfThenable}, for a value being **discarded**, where
|
|
580
|
+
* there is no Defect channel to route to and the only correct answer is to
|
|
581
|
+
* drop it without throwing.
|
|
582
|
+
*
|
|
583
|
+
* Calling it bare, outside both, is a bug: the throw escapes into whatever
|
|
584
|
+
* context invoked it.
|
|
585
|
+
*
|
|
586
|
+
* @internal
|
|
587
|
+
*/
|
|
588
|
+
function isThenable(x) {
|
|
589
|
+
return (typeof x === "object" || typeof x === "function") && x !== null && typeof x.then === "function";
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
571
592
|
* Adopt-and-silence a thenable a combinator is about to **discard**.
|
|
572
593
|
*
|
|
573
594
|
* @remarks
|
|
@@ -588,7 +609,7 @@ function passThrough(self) {
|
|
|
588
609
|
*/
|
|
589
610
|
function silenceIfThenable(value) {
|
|
590
611
|
try {
|
|
591
|
-
if ((
|
|
612
|
+
if (isThenable(value)) Promise.resolve(value).then(void 0, () => void 0);
|
|
592
613
|
} catch {}
|
|
593
614
|
}
|
|
594
615
|
/**
|
|
@@ -669,18 +690,29 @@ var AsyncRes = class AsyncRes {
|
|
|
669
690
|
constructor(promise) {
|
|
670
691
|
this.#promise = promise;
|
|
671
692
|
}
|
|
693
|
+
/**
|
|
694
|
+
* Lift a **synchronous** `Result` combinator over the wrapped promise.
|
|
695
|
+
*
|
|
696
|
+
* @remarks
|
|
697
|
+
* Every method below that does no `await` of its own is exactly its `Res`
|
|
698
|
+
* twin applied to the settled `Result` — same tag check, same try/catch, same
|
|
699
|
+
* throw→defect net. Delegating instead of restating it keeps the two surfaces
|
|
700
|
+
* from drifting: a fix to the sync combinator is the fix to the async one.
|
|
701
|
+
* The methods that DO await a callback's result (`flatMap`, `flatTap`,
|
|
702
|
+
* `bind`, `flatMapErrCases`, `flatTapErrCases`, `recoverDefect` — the ones
|
|
703
|
+
* whose callback may hand back an `AsyncResult`) genuinely differ and are
|
|
704
|
+
* still written out in full.
|
|
705
|
+
*
|
|
706
|
+
* @internal
|
|
707
|
+
*/
|
|
708
|
+
#lift(f) {
|
|
709
|
+
return new AsyncRes(this.#promise.then(f));
|
|
710
|
+
}
|
|
672
711
|
then(onfulfilled, onrejected) {
|
|
673
712
|
return this.#promise.then(onfulfilled, onrejected);
|
|
674
713
|
}
|
|
675
714
|
map(f) {
|
|
676
|
-
return
|
|
677
|
-
if (r.tag !== "Ok") return passThrough(r);
|
|
678
|
-
try {
|
|
679
|
-
return okRes(f(r.value));
|
|
680
|
-
} catch (cause) {
|
|
681
|
-
return defectRes(cause);
|
|
682
|
-
}
|
|
683
|
-
}));
|
|
715
|
+
return this.#lift((r) => r.map(f));
|
|
684
716
|
}
|
|
685
717
|
flatMap(f) {
|
|
686
718
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -694,15 +726,7 @@ var AsyncRes = class AsyncRes {
|
|
|
694
726
|
}));
|
|
695
727
|
}
|
|
696
728
|
tap(f) {
|
|
697
|
-
return
|
|
698
|
-
if (r.tag !== "Ok") return r;
|
|
699
|
-
try {
|
|
700
|
-
silenceIfThenable(f(r.value));
|
|
701
|
-
return r;
|
|
702
|
-
} catch (cause) {
|
|
703
|
-
return defectRes(cause);
|
|
704
|
-
}
|
|
705
|
-
}));
|
|
729
|
+
return this.#lift((r) => r.tap(f));
|
|
706
730
|
}
|
|
707
731
|
flatTap(f) {
|
|
708
732
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -733,45 +757,19 @@ var AsyncRes = class AsyncRes {
|
|
|
733
757
|
}));
|
|
734
758
|
}
|
|
735
759
|
let(name, f) {
|
|
736
|
-
return
|
|
737
|
-
if (r.tag !== "Ok") return passThrough(r);
|
|
738
|
-
try {
|
|
739
|
-
return okRes({
|
|
740
|
-
...scopeOf(r.value),
|
|
741
|
-
[name]: f(r.value)
|
|
742
|
-
});
|
|
743
|
-
} catch (cause) {
|
|
744
|
-
return defectRes(cause);
|
|
745
|
-
}
|
|
746
|
-
}));
|
|
760
|
+
return this.#lift((r) => r.let(name, f));
|
|
747
761
|
}
|
|
748
762
|
as(value) {
|
|
749
|
-
return
|
|
763
|
+
return this.#lift((r) => r.as(value));
|
|
750
764
|
}
|
|
751
765
|
discard() {
|
|
752
|
-
return
|
|
766
|
+
return this.#lift((r) => r.discard());
|
|
753
767
|
}
|
|
754
768
|
ensure(predicate, onFail) {
|
|
755
|
-
return
|
|
756
|
-
if (r.tag !== "Ok") return passThrough(r);
|
|
757
|
-
try {
|
|
758
|
-
return predicate(r.value) ? r : errRes(onFail(r.value));
|
|
759
|
-
} catch (cause) {
|
|
760
|
-
return defectRes(cause);
|
|
761
|
-
}
|
|
762
|
-
}));
|
|
769
|
+
return this.#lift((r) => r.ensure(predicate, onFail));
|
|
763
770
|
}
|
|
764
771
|
mapErrCases(f) {
|
|
765
|
-
return
|
|
766
|
-
if (r.tag !== "Err") return passThrough(r);
|
|
767
|
-
try {
|
|
768
|
-
const out = runMatch(f, r.error);
|
|
769
|
-
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
770
|
-
return errRes(out);
|
|
771
|
-
} catch (cause) {
|
|
772
|
-
return defectRes(cause);
|
|
773
|
-
}
|
|
774
|
-
}));
|
|
772
|
+
return this.#lift((r) => r.mapErrCases(f));
|
|
775
773
|
}
|
|
776
774
|
flatMapErrCases(f) {
|
|
777
775
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -788,29 +786,10 @@ var AsyncRes = class AsyncRes {
|
|
|
788
786
|
}));
|
|
789
787
|
}
|
|
790
788
|
recoverErrCases(f) {
|
|
791
|
-
return
|
|
792
|
-
if (r.tag !== "Err") return passThrough(r);
|
|
793
|
-
try {
|
|
794
|
-
const out = runMatch(f, r.error);
|
|
795
|
-
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
796
|
-
return okRes(out);
|
|
797
|
-
} catch (cause) {
|
|
798
|
-
return defectRes(cause);
|
|
799
|
-
}
|
|
800
|
-
}));
|
|
789
|
+
return this.#lift((r) => r.recoverErrCases(f));
|
|
801
790
|
}
|
|
802
791
|
tapErrCases(f) {
|
|
803
|
-
return
|
|
804
|
-
if (r.tag !== "Err") return r;
|
|
805
|
-
try {
|
|
806
|
-
const out = runMatch(f, r.error);
|
|
807
|
-
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
808
|
-
silenceIfThenable(out);
|
|
809
|
-
return r;
|
|
810
|
-
} catch (cause) {
|
|
811
|
-
return observerThrowToDefect(cause, r.error);
|
|
812
|
-
}
|
|
813
|
-
}));
|
|
792
|
+
return this.#lift((r) => r.tapErrCases(f));
|
|
814
793
|
}
|
|
815
794
|
flatTapErrCases(f) {
|
|
816
795
|
return new AsyncRes(this.#promise.then(async (r) => {
|
|
@@ -838,26 +817,10 @@ var AsyncRes = class AsyncRes {
|
|
|
838
817
|
}));
|
|
839
818
|
}
|
|
840
819
|
tapDefect(f) {
|
|
841
|
-
return
|
|
842
|
-
if (r.tag !== "Defect") return r;
|
|
843
|
-
try {
|
|
844
|
-
silenceIfThenable(f(r.cause));
|
|
845
|
-
return r;
|
|
846
|
-
} catch (cause) {
|
|
847
|
-
return observerThrowToDefect(cause, r.cause);
|
|
848
|
-
}
|
|
849
|
-
}));
|
|
820
|
+
return this.#lift((r) => r.tapDefect(f));
|
|
850
821
|
}
|
|
851
822
|
tapFailure(f) {
|
|
852
|
-
return
|
|
853
|
-
if (r.tag === "Ok") return r;
|
|
854
|
-
try {
|
|
855
|
-
silenceIfThenable(f(r));
|
|
856
|
-
return r;
|
|
857
|
-
} catch (cause) {
|
|
858
|
-
return observerThrowToDefect(cause, r.tag === "Err" ? r.error : r.cause);
|
|
859
|
-
}
|
|
860
|
-
}));
|
|
823
|
+
return this.#lift((r) => r.tapFailure(f));
|
|
861
824
|
}
|
|
862
825
|
match(cases) {
|
|
863
826
|
return this.#promise.then((r) => r.match(cases));
|
|
@@ -1339,7 +1302,7 @@ function fromExecutor(executor) {
|
|
|
1339
1302
|
return;
|
|
1340
1303
|
}
|
|
1341
1304
|
if (!isResult(result)) {
|
|
1342
|
-
|
|
1305
|
+
silenceIfThenable(result);
|
|
1343
1306
|
resolve(defectRes(/* @__PURE__ */ new TypeError("unthrown: fromExecutor's settle received a non-Result value")));
|
|
1344
1307
|
return;
|
|
1345
1308
|
}
|
|
@@ -1393,15 +1356,6 @@ function thenableReturnDefect(value) {
|
|
|
1393
1356
|
return defectRes(/* @__PURE__ */ new TypeError("unthrown: fromThrowable/fromSafeThrowable wrap a SYNCHRONOUS function, but `fn` returned a thenable — its rejection would escape qualification. Use fromPromise/fromSafePromise instead."));
|
|
1394
1357
|
}
|
|
1395
1358
|
/**
|
|
1396
|
-
* Runtime thenable probe for the belt-and-braces guards above. Called inside the
|
|
1397
|
-
* caller's `try`, so even a hostile `.then` getter lands on the Defect path.
|
|
1398
|
-
*
|
|
1399
|
-
* @internal
|
|
1400
|
-
*/
|
|
1401
|
-
function isThenable(x) {
|
|
1402
|
-
return (typeof x === "object" || typeof x === "function") && x !== null && typeof x.then === "function";
|
|
1403
|
-
}
|
|
1404
|
-
/**
|
|
1405
1359
|
* Fold an array of settled `Result`s: first `Err` wins, any `Defect` dominates,
|
|
1406
1360
|
* else `Ok` of the values array.
|
|
1407
1361
|
*
|
|
@@ -1430,32 +1384,23 @@ function foldArray(results) {
|
|
|
1430
1384
|
}
|
|
1431
1385
|
/**
|
|
1432
1386
|
* Fold a record of settled `Result`s with the same rules, else `Ok` of the
|
|
1433
|
-
* record of values.
|
|
1434
|
-
*
|
|
1387
|
+
* record of values.
|
|
1388
|
+
*
|
|
1389
|
+
* @remarks
|
|
1390
|
+
* The positional fold already implements every rule (first `Err` wins, any
|
|
1391
|
+
* `Defect` dominates, a non-`Result` element becomes a `Defect`), so this pairs
|
|
1392
|
+
* the keys back onto its success value rather than restating them.
|
|
1393
|
+
*
|
|
1394
|
+
* `Object.fromEntries` is what makes a caller-supplied `"__proto__"` key safe:
|
|
1395
|
+
* it builds each key with CreateDataProperty, which defines an own property
|
|
1396
|
+
* instead of invoking the `__proto__` setter — the same guarantee the previous
|
|
1397
|
+
* explicit `Object.defineProperty` loop bought by hand.
|
|
1435
1398
|
*
|
|
1436
1399
|
* @internal
|
|
1437
1400
|
*/
|
|
1438
1401
|
function foldRecord(results) {
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
const values = {};
|
|
1442
|
-
for (const [key, r] of Object.entries(results)) {
|
|
1443
|
-
if (!isResult(r)) {
|
|
1444
|
-
firstDefect ??= nonResultDefect();
|
|
1445
|
-
break;
|
|
1446
|
-
}
|
|
1447
|
-
if (r.tag === "Defect") {
|
|
1448
|
-
firstDefect ??= r;
|
|
1449
|
-
break;
|
|
1450
|
-
} else if (r.tag === "Err") firstErr ??= r;
|
|
1451
|
-
else Object.defineProperty(values, key, {
|
|
1452
|
-
value: r.value,
|
|
1453
|
-
enumerable: true,
|
|
1454
|
-
writable: true,
|
|
1455
|
-
configurable: true
|
|
1456
|
-
});
|
|
1457
|
-
}
|
|
1458
|
-
return firstDefect ?? firstErr ?? Ok(values);
|
|
1402
|
+
const keys = Object.keys(results);
|
|
1403
|
+
return foldArray(Object.values(results)).map((values) => Object.fromEntries(keys.map((key, i) => [key, values[i]])));
|
|
1459
1404
|
}
|
|
1460
1405
|
/**
|
|
1461
1406
|
* Collect a tuple/array of {@link Result}s into a single `Result` of all their
|
|
@@ -1553,14 +1498,8 @@ function allAsync(results) {
|
|
|
1553
1498
|
* ```
|
|
1554
1499
|
*/
|
|
1555
1500
|
function allFromDictAsync(results) {
|
|
1556
|
-
const
|
|
1557
|
-
return new AsyncRes(Promise.all(
|
|
1558
|
-
const byKey = Object.create(null);
|
|
1559
|
-
entries.forEach(([key], i) => {
|
|
1560
|
-
byKey[key] = resolved[i];
|
|
1561
|
-
});
|
|
1562
|
-
return foldRecord(byKey);
|
|
1563
|
-
}));
|
|
1501
|
+
const keys = Object.keys(results);
|
|
1502
|
+
return new AsyncRes(Promise.all(Object.values(results).map((ar) => Promise.resolve(ar).then((x) => x, (cause) => defectRes(cause)))).then((resolved) => foldArray(resolved).map((values) => Object.fromEntries(keys.map((key, i) => [key, values[i]])))));
|
|
1564
1503
|
}
|
|
1565
1504
|
//#endregion
|
|
1566
1505
|
//#region src/facade.ts
|