unthrown 5.1.0 → 5.3.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 +87 -8
- package/dist/index.d.cts +88 -15
- package/dist/index.d.mts +88 -15
- package/dist/index.mjs +87 -9
- package/package.json +4 -8
package/dist/index.cjs
CHANGED
|
@@ -538,7 +538,11 @@ function defectRes(cause) {
|
|
|
538
538
|
* // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
|
|
539
539
|
* // so the `P._` escape hatch is the only arm that can terminate the match:
|
|
540
540
|
* // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
|
|
541
|
-
* x.match({
|
|
541
|
+
* x.match({
|
|
542
|
+
* ok: () => 1,
|
|
543
|
+
* errCases: (m) => m.with(P._, () => 0),
|
|
544
|
+
* defect: () => -1,
|
|
545
|
+
* });
|
|
542
546
|
* ```
|
|
543
547
|
*
|
|
544
548
|
* @category Guards
|
|
@@ -1284,6 +1288,72 @@ function fromPromise(promise, qualify, ..._guard) {
|
|
|
1284
1288
|
function fromSafePromise(promise) {
|
|
1285
1289
|
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : Promise.resolve(promise)).then((value) => okRes(value), (cause) => defectRes(cause)));
|
|
1286
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Build an {@link AsyncResult} from a callback-style API — this library's
|
|
1293
|
+
* answer to `new Promise((resolve, reject) => …)`.
|
|
1294
|
+
*
|
|
1295
|
+
* @remarks
|
|
1296
|
+
* The settler takes a **`Result`**, not a value-or-reason pair: the caller names
|
|
1297
|
+
* the variant, so no `unknown` can enter `E` and there is no `qualify` to pass.
|
|
1298
|
+
* For a failure that is *not* modeled, settle the injected `defect` helper's
|
|
1299
|
+
* marker — the same injection `qualify` receives, and the only way to reach the
|
|
1300
|
+
* defect channel from inside an asynchronous callback (a `throw` there runs in
|
|
1301
|
+
* its own turn, long after the executor body returned).
|
|
1302
|
+
*
|
|
1303
|
+
* `T` and `E` cannot be inferred from the body, since `settle` is a parameter.
|
|
1304
|
+
* Supply them explicitly, or let them flow from an annotated target. Absent
|
|
1305
|
+
* either, both default to `never` (Thesis #3: no path may produce `unknown` in
|
|
1306
|
+
* `E`) — so an unannotated call is a compile error at the `settle(...)` call
|
|
1307
|
+
* site, not a silently-`unknown` channel.
|
|
1308
|
+
*
|
|
1309
|
+
* An executor that never settles yields an `AsyncResult` that never resolves —
|
|
1310
|
+
* the one hazard {@link fromPromise} does not have, and identical to
|
|
1311
|
+
* `new Promise`.
|
|
1312
|
+
*
|
|
1313
|
+
* @typeParam T - the success type.
|
|
1314
|
+
* @typeParam E - the modeled error type.
|
|
1315
|
+
* @param executor - runs immediately; receives the settler and the `defect` helper.
|
|
1316
|
+
*
|
|
1317
|
+
* @category Interop
|
|
1318
|
+
*
|
|
1319
|
+
* @example
|
|
1320
|
+
* ```ts
|
|
1321
|
+
* import { fromExecutor, Err, Ok } from "unthrown";
|
|
1322
|
+
*
|
|
1323
|
+
* const listen = (port: number) =>
|
|
1324
|
+
* fromExecutor<Server, PortInUse>((settle, defect) => {
|
|
1325
|
+
* server.once("error", (cause) =>
|
|
1326
|
+
* isAddrInUse(cause) ? settle(Err(new PortInUse(port))) : settle(defect(cause)),
|
|
1327
|
+
* );
|
|
1328
|
+
* server.listen(port, () => settle(Ok(server)));
|
|
1329
|
+
* });
|
|
1330
|
+
* ```
|
|
1331
|
+
*/
|
|
1332
|
+
function fromExecutor(executor) {
|
|
1333
|
+
let resolve;
|
|
1334
|
+
const promise = new Promise((r) => {
|
|
1335
|
+
resolve = r;
|
|
1336
|
+
});
|
|
1337
|
+
const settle = (result) => {
|
|
1338
|
+
if (isDefectMarker(result)) {
|
|
1339
|
+
resolve(defectRes(result.cause));
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
if (!isResult(result)) {
|
|
1343
|
+
if (isThenable(result)) Promise.resolve(result).then(void 0, () => void 0);
|
|
1344
|
+
resolve(defectRes(/* @__PURE__ */ new TypeError("unthrown: fromExecutor's settle received a non-Result value")));
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
resolve(result);
|
|
1348
|
+
};
|
|
1349
|
+
try {
|
|
1350
|
+
const returned = executor(settle, defect);
|
|
1351
|
+
if (isThenable(returned)) Promise.resolve(returned).then(void 0, (cause) => settle(defect(cause)));
|
|
1352
|
+
} catch (cause) {
|
|
1353
|
+
settle(defect(cause));
|
|
1354
|
+
}
|
|
1355
|
+
return new AsyncRes(promise);
|
|
1356
|
+
}
|
|
1287
1357
|
function qualifyToResult(cause, qualify) {
|
|
1288
1358
|
try {
|
|
1289
1359
|
const q = qualify(cause, defect);
|
|
@@ -1452,7 +1522,10 @@ function allFromDict(results) {
|
|
|
1452
1522
|
* ```ts
|
|
1453
1523
|
* import { allAsync, fromSafePromise } from "unthrown";
|
|
1454
1524
|
*
|
|
1455
|
-
* const both = allAsync([
|
|
1525
|
+
* const both = allAsync([
|
|
1526
|
+
* fromSafePromise(Promise.resolve(1)),
|
|
1527
|
+
* fromSafePromise(Promise.resolve(2)),
|
|
1528
|
+
* ]);
|
|
1456
1529
|
* (await both).get(); // => [1, 2]
|
|
1457
1530
|
* ```
|
|
1458
1531
|
*/
|
|
@@ -1535,14 +1608,15 @@ const Result = {
|
|
|
1535
1608
|
/**
|
|
1536
1609
|
* Companion object grouping the **`AsyncResult`-producing** entry points under
|
|
1537
1610
|
* the matching namespace: {@link AsyncResult.Ok}, {@link AsyncResult.Err},
|
|
1538
|
-
* {@link AsyncResult.Do}, {@link AsyncResult.
|
|
1539
|
-
* {@link AsyncResult.
|
|
1540
|
-
* {@link AsyncResult.allFromDict}.
|
|
1611
|
+
* {@link AsyncResult.Do}, {@link AsyncResult.fromExecutor},
|
|
1612
|
+
* {@link AsyncResult.fromPromise}, {@link AsyncResult.fromSafePromise},
|
|
1613
|
+
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict}.
|
|
1541
1614
|
*
|
|
1542
1615
|
* @remarks
|
|
1543
1616
|
* The async sibling of {@link Result}. Statics are grouped by what they
|
|
1544
|
-
* **return**, so the pre-lifted constructors, `
|
|
1545
|
-
* and the async aggregates sit here rather
|
|
1617
|
+
* **return**, so the pre-lifted constructors, `fromExecutor`,
|
|
1618
|
+
* `fromPromise`/`fromSafePromise`, and the async aggregates sit here rather
|
|
1619
|
+
* than on {@link Result}; the namespace
|
|
1546
1620
|
* already conveys "async", so the members drop the `Async` suffix their free
|
|
1547
1621
|
* functions carry (`AsyncResult.Ok` is `OkAsync`; `AsyncResult.Err` is
|
|
1548
1622
|
* `ErrAsync`; `AsyncResult.Do` is `DoAsync`; `AsyncResult.all` is `allAsync`;
|
|
@@ -1556,7 +1630,10 @@ const Result = {
|
|
|
1556
1630
|
* @example
|
|
1557
1631
|
* ```ts
|
|
1558
1632
|
* import { AsyncResult } from "unthrown";
|
|
1559
|
-
* const user = await AsyncResult.fromPromise(
|
|
1633
|
+
* const user = await AsyncResult.fromPromise(
|
|
1634
|
+
* fetchUser(id),
|
|
1635
|
+
* (c, defect) => defect(c),
|
|
1636
|
+
* );
|
|
1560
1637
|
* user.get(); // => the fetched user (on success)
|
|
1561
1638
|
* ```
|
|
1562
1639
|
*/
|
|
@@ -1564,6 +1641,7 @@ const AsyncResult = {
|
|
|
1564
1641
|
Ok: OkAsync,
|
|
1565
1642
|
Err: ErrAsync,
|
|
1566
1643
|
Do: DoAsync,
|
|
1644
|
+
fromExecutor,
|
|
1567
1645
|
fromPromise,
|
|
1568
1646
|
fromSafePromise,
|
|
1569
1647
|
all: allAsync,
|
|
@@ -1676,6 +1754,7 @@ exports.all = all;
|
|
|
1676
1754
|
exports.allAsync = allAsync;
|
|
1677
1755
|
exports.allFromDict = allFromDict;
|
|
1678
1756
|
exports.allFromDictAsync = allFromDictAsync;
|
|
1757
|
+
exports.fromExecutor = fromExecutor;
|
|
1679
1758
|
exports.fromNullable = fromNullable;
|
|
1680
1759
|
exports.fromPromise = fromPromise;
|
|
1681
1760
|
exports.fromSafePromise = fromSafePromise;
|
package/dist/index.d.cts
CHANGED
|
@@ -766,12 +766,20 @@ type ResultMethods<out T, out E> = {
|
|
|
766
766
|
*
|
|
767
767
|
* @remarks
|
|
768
768
|
* A deliberate escape hatch off the errors-as-values model — it **throws the
|
|
769
|
-
* `Err` value as-is** at the call site
|
|
770
|
-
*
|
|
771
|
-
* this
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
769
|
+
* `Err` value as-is** at the call site, so a caller of the enclosing function
|
|
770
|
+
* sees a throw rather than a channel. Its home is **tests and scripts**,
|
|
771
|
+
* where "this `Result` had better be `Ok`" is the assertion and a throw is
|
|
772
|
+
* the correct failure mode.
|
|
773
|
+
*
|
|
774
|
+
* In production code, fold the error channel instead:
|
|
775
|
+
* {@link ResultMethods.recoverErrCases | recoverErrCases} empties `E`, so
|
|
776
|
+
* {@link ResultMethods.get | get} compiles and a case routed to the injected
|
|
777
|
+
* `defect(...)` panics with its original cause — with every case still named.
|
|
778
|
+
* {@link ResultMethods.match | match} and
|
|
779
|
+
* {@link ResultMethods.flatMapErrCases | flatMapErrCases} are the other two
|
|
780
|
+
* ways to keep the error a value. `@unthrown/oxlint`'s opt-in
|
|
781
|
+
* `no-get-or-throw` rule enforces this, exempting test files through an
|
|
782
|
+
* oxlint `overrides` entry.
|
|
775
783
|
*
|
|
776
784
|
* Type-gated as the **complement** of {@link ResultMethods.get | get}: it
|
|
777
785
|
* compiles only when the error channel is **non-empty** (`E` is not `never`) —
|
|
@@ -1450,7 +1458,11 @@ declare class GetError<E = unknown> extends Error {
|
|
|
1450
1458
|
* // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
|
|
1451
1459
|
* // so the `P._` escape hatch is the only arm that can terminate the match:
|
|
1452
1460
|
* // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
|
|
1453
|
-
* x.match({
|
|
1461
|
+
* x.match({
|
|
1462
|
+
* ok: () => 1,
|
|
1463
|
+
* errCases: (m) => m.with(P._, () => 0),
|
|
1464
|
+
* defect: () => -1,
|
|
1465
|
+
* });
|
|
1454
1466
|
* ```
|
|
1455
1467
|
*
|
|
1456
1468
|
* @category Guards
|
|
@@ -1708,6 +1720,59 @@ declare function fromPromise<T, R>(promise: Promise<T> | (() => Promise<T>), qua
|
|
|
1708
1720
|
* ```
|
|
1709
1721
|
*/
|
|
1710
1722
|
declare function fromSafePromise<T>(promise: Promise<T> | (() => Promise<T>)): AsyncResult$1<T, never>;
|
|
1723
|
+
/**
|
|
1724
|
+
* The settler a {@link fromExecutor} executor receives. Settles the pending
|
|
1725
|
+
* `AsyncResult` **once** — later calls are no-ops, exactly as `resolve` is on a
|
|
1726
|
+
* `Promise`.
|
|
1727
|
+
*
|
|
1728
|
+
* @typeParam T - the success type.
|
|
1729
|
+
* @typeParam E - the modeled error type.
|
|
1730
|
+
*
|
|
1731
|
+
* @category Types
|
|
1732
|
+
*/
|
|
1733
|
+
type Settle<T, E> = (result: Result$1<T, E> | Defect) => void;
|
|
1734
|
+
/**
|
|
1735
|
+
* Build an {@link AsyncResult} from a callback-style API — this library's
|
|
1736
|
+
* answer to `new Promise((resolve, reject) => …)`.
|
|
1737
|
+
*
|
|
1738
|
+
* @remarks
|
|
1739
|
+
* The settler takes a **`Result`**, not a value-or-reason pair: the caller names
|
|
1740
|
+
* the variant, so no `unknown` can enter `E` and there is no `qualify` to pass.
|
|
1741
|
+
* For a failure that is *not* modeled, settle the injected `defect` helper's
|
|
1742
|
+
* marker — the same injection `qualify` receives, and the only way to reach the
|
|
1743
|
+
* defect channel from inside an asynchronous callback (a `throw` there runs in
|
|
1744
|
+
* its own turn, long after the executor body returned).
|
|
1745
|
+
*
|
|
1746
|
+
* `T` and `E` cannot be inferred from the body, since `settle` is a parameter.
|
|
1747
|
+
* Supply them explicitly, or let them flow from an annotated target. Absent
|
|
1748
|
+
* either, both default to `never` (Thesis #3: no path may produce `unknown` in
|
|
1749
|
+
* `E`) — so an unannotated call is a compile error at the `settle(...)` call
|
|
1750
|
+
* site, not a silently-`unknown` channel.
|
|
1751
|
+
*
|
|
1752
|
+
* An executor that never settles yields an `AsyncResult` that never resolves —
|
|
1753
|
+
* the one hazard {@link fromPromise} does not have, and identical to
|
|
1754
|
+
* `new Promise`.
|
|
1755
|
+
*
|
|
1756
|
+
* @typeParam T - the success type.
|
|
1757
|
+
* @typeParam E - the modeled error type.
|
|
1758
|
+
* @param executor - runs immediately; receives the settler and the `defect` helper.
|
|
1759
|
+
*
|
|
1760
|
+
* @category Interop
|
|
1761
|
+
*
|
|
1762
|
+
* @example
|
|
1763
|
+
* ```ts
|
|
1764
|
+
* import { fromExecutor, Err, Ok } from "unthrown";
|
|
1765
|
+
*
|
|
1766
|
+
* const listen = (port: number) =>
|
|
1767
|
+
* fromExecutor<Server, PortInUse>((settle, defect) => {
|
|
1768
|
+
* server.once("error", (cause) =>
|
|
1769
|
+
* isAddrInUse(cause) ? settle(Err(new PortInUse(port))) : settle(defect(cause)),
|
|
1770
|
+
* );
|
|
1771
|
+
* server.listen(port, () => settle(Ok(server)));
|
|
1772
|
+
* });
|
|
1773
|
+
* ```
|
|
1774
|
+
*/
|
|
1775
|
+
declare function fromExecutor<T = never, E = never>(executor: (settle: Settle<T, E>, defect: (cause: unknown) => Defect) => void): AsyncResult$1<T, E>;
|
|
1711
1776
|
/**
|
|
1712
1777
|
* The success channel of {@link all} / {@link allAsync}: a **positional tuple**
|
|
1713
1778
|
* for a fixed-length input (including the empty tuple), or a homogeneous
|
|
@@ -1790,7 +1855,10 @@ declare function allFromDict<R extends ResultRecord>(results: R): Result$1<{ [K
|
|
|
1790
1855
|
* ```ts
|
|
1791
1856
|
* import { allAsync, fromSafePromise } from "unthrown";
|
|
1792
1857
|
*
|
|
1793
|
-
* const both = allAsync([
|
|
1858
|
+
* const both = allAsync([
|
|
1859
|
+
* fromSafePromise(Promise.resolve(1)),
|
|
1860
|
+
* fromSafePromise(Promise.resolve(2)),
|
|
1861
|
+
* ]);
|
|
1794
1862
|
* (await both).get(); // => [1, 2]
|
|
1795
1863
|
* ```
|
|
1796
1864
|
*/
|
|
@@ -1877,14 +1945,15 @@ type Result<T, E> = Result$1<T, E>;
|
|
|
1877
1945
|
/**
|
|
1878
1946
|
* Companion object grouping the **`AsyncResult`-producing** entry points under
|
|
1879
1947
|
* the matching namespace: {@link AsyncResult.Ok}, {@link AsyncResult.Err},
|
|
1880
|
-
* {@link AsyncResult.Do}, {@link AsyncResult.
|
|
1881
|
-
* {@link AsyncResult.
|
|
1882
|
-
* {@link AsyncResult.allFromDict}.
|
|
1948
|
+
* {@link AsyncResult.Do}, {@link AsyncResult.fromExecutor},
|
|
1949
|
+
* {@link AsyncResult.fromPromise}, {@link AsyncResult.fromSafePromise},
|
|
1950
|
+
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict}.
|
|
1883
1951
|
*
|
|
1884
1952
|
* @remarks
|
|
1885
1953
|
* The async sibling of {@link Result}. Statics are grouped by what they
|
|
1886
|
-
* **return**, so the pre-lifted constructors, `
|
|
1887
|
-
* and the async aggregates sit here rather
|
|
1954
|
+
* **return**, so the pre-lifted constructors, `fromExecutor`,
|
|
1955
|
+
* `fromPromise`/`fromSafePromise`, and the async aggregates sit here rather
|
|
1956
|
+
* than on {@link Result}; the namespace
|
|
1888
1957
|
* already conveys "async", so the members drop the `Async` suffix their free
|
|
1889
1958
|
* functions carry (`AsyncResult.Ok` is `OkAsync`; `AsyncResult.Err` is
|
|
1890
1959
|
* `ErrAsync`; `AsyncResult.Do` is `DoAsync`; `AsyncResult.all` is `allAsync`;
|
|
@@ -1898,7 +1967,10 @@ type Result<T, E> = Result$1<T, E>;
|
|
|
1898
1967
|
* @example
|
|
1899
1968
|
* ```ts
|
|
1900
1969
|
* import { AsyncResult } from "unthrown";
|
|
1901
|
-
* const user = await AsyncResult.fromPromise(
|
|
1970
|
+
* const user = await AsyncResult.fromPromise(
|
|
1971
|
+
* fetchUser(id),
|
|
1972
|
+
* (c, defect) => defect(c),
|
|
1973
|
+
* );
|
|
1902
1974
|
* user.get(); // => the fetched user (on success)
|
|
1903
1975
|
* ```
|
|
1904
1976
|
*/
|
|
@@ -1906,6 +1978,7 @@ declare const AsyncResult: {
|
|
|
1906
1978
|
readonly Ok: typeof OkAsync;
|
|
1907
1979
|
readonly Err: typeof ErrAsync;
|
|
1908
1980
|
readonly Do: typeof DoAsync;
|
|
1981
|
+
readonly fromExecutor: typeof fromExecutor;
|
|
1909
1982
|
readonly fromPromise: typeof fromPromise;
|
|
1910
1983
|
readonly fromSafePromise: typeof fromSafePromise;
|
|
1911
1984
|
readonly all: typeof allAsync;
|
|
@@ -2034,4 +2107,4 @@ declare function TaggedError<Tag extends string>(tag: Tag, options?: {
|
|
|
2034
2107
|
readonly name?: string;
|
|
2035
2108
|
}): TaggedErrorConstructor<Tag>;
|
|
2036
2109
|
//#endregion
|
|
2037
|
-
export { type AsyncErrOf, type AsyncOkOf, AsyncResult, type AsyncResultMethods, type Awaitable, type DefectView, Do, DoAsync, Err, ErrAsync, type ErrMatcher, type ErrOf, type ErrView, type FailureView, GetError, type Matcher, NonExhaustiveError, type NotThenable, Ok, OkAsync, type OkOf, type OkView, P, type PatternMatcher, Result, type ResultMethods, TaggedError, type TaggedErrorConstructor, type TaggedErrorInstance, type UniversalPattern, all, allAsync, allFromDict, allFromDictAsync, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, match };
|
|
2110
|
+
export { type AsyncErrOf, type AsyncOkOf, AsyncResult, type AsyncResultMethods, type Awaitable, type DefectView, Do, DoAsync, Err, ErrAsync, type ErrMatcher, type ErrOf, type ErrView, type FailureView, GetError, type Matcher, NonExhaustiveError, type NotThenable, Ok, OkAsync, type OkOf, type OkView, P, type PatternMatcher, Result, type ResultMethods, type Settle, TaggedError, type TaggedErrorConstructor, type TaggedErrorInstance, type UniversalPattern, all, allAsync, allFromDict, allFromDictAsync, fromExecutor, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, match };
|
package/dist/index.d.mts
CHANGED
|
@@ -766,12 +766,20 @@ type ResultMethods<out T, out E> = {
|
|
|
766
766
|
*
|
|
767
767
|
* @remarks
|
|
768
768
|
* A deliberate escape hatch off the errors-as-values model — it **throws the
|
|
769
|
-
* `Err` value as-is** at the call site
|
|
770
|
-
*
|
|
771
|
-
* this
|
|
772
|
-
*
|
|
773
|
-
*
|
|
774
|
-
*
|
|
769
|
+
* `Err` value as-is** at the call site, so a caller of the enclosing function
|
|
770
|
+
* sees a throw rather than a channel. Its home is **tests and scripts**,
|
|
771
|
+
* where "this `Result` had better be `Ok`" is the assertion and a throw is
|
|
772
|
+
* the correct failure mode.
|
|
773
|
+
*
|
|
774
|
+
* In production code, fold the error channel instead:
|
|
775
|
+
* {@link ResultMethods.recoverErrCases | recoverErrCases} empties `E`, so
|
|
776
|
+
* {@link ResultMethods.get | get} compiles and a case routed to the injected
|
|
777
|
+
* `defect(...)` panics with its original cause — with every case still named.
|
|
778
|
+
* {@link ResultMethods.match | match} and
|
|
779
|
+
* {@link ResultMethods.flatMapErrCases | flatMapErrCases} are the other two
|
|
780
|
+
* ways to keep the error a value. `@unthrown/oxlint`'s opt-in
|
|
781
|
+
* `no-get-or-throw` rule enforces this, exempting test files through an
|
|
782
|
+
* oxlint `overrides` entry.
|
|
775
783
|
*
|
|
776
784
|
* Type-gated as the **complement** of {@link ResultMethods.get | get}: it
|
|
777
785
|
* compiles only when the error channel is **non-empty** (`E` is not `never`) —
|
|
@@ -1450,7 +1458,11 @@ declare class GetError<E = unknown> extends Error {
|
|
|
1450
1458
|
* // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
|
|
1451
1459
|
* // so the `P._` escape hatch is the only arm that can terminate the match:
|
|
1452
1460
|
* // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
|
|
1453
|
-
* x.match({
|
|
1461
|
+
* x.match({
|
|
1462
|
+
* ok: () => 1,
|
|
1463
|
+
* errCases: (m) => m.with(P._, () => 0),
|
|
1464
|
+
* defect: () => -1,
|
|
1465
|
+
* });
|
|
1454
1466
|
* ```
|
|
1455
1467
|
*
|
|
1456
1468
|
* @category Guards
|
|
@@ -1708,6 +1720,59 @@ declare function fromPromise<T, R>(promise: Promise<T> | (() => Promise<T>), qua
|
|
|
1708
1720
|
* ```
|
|
1709
1721
|
*/
|
|
1710
1722
|
declare function fromSafePromise<T>(promise: Promise<T> | (() => Promise<T>)): AsyncResult$1<T, never>;
|
|
1723
|
+
/**
|
|
1724
|
+
* The settler a {@link fromExecutor} executor receives. Settles the pending
|
|
1725
|
+
* `AsyncResult` **once** — later calls are no-ops, exactly as `resolve` is on a
|
|
1726
|
+
* `Promise`.
|
|
1727
|
+
*
|
|
1728
|
+
* @typeParam T - the success type.
|
|
1729
|
+
* @typeParam E - the modeled error type.
|
|
1730
|
+
*
|
|
1731
|
+
* @category Types
|
|
1732
|
+
*/
|
|
1733
|
+
type Settle<T, E> = (result: Result$1<T, E> | Defect) => void;
|
|
1734
|
+
/**
|
|
1735
|
+
* Build an {@link AsyncResult} from a callback-style API — this library's
|
|
1736
|
+
* answer to `new Promise((resolve, reject) => …)`.
|
|
1737
|
+
*
|
|
1738
|
+
* @remarks
|
|
1739
|
+
* The settler takes a **`Result`**, not a value-or-reason pair: the caller names
|
|
1740
|
+
* the variant, so no `unknown` can enter `E` and there is no `qualify` to pass.
|
|
1741
|
+
* For a failure that is *not* modeled, settle the injected `defect` helper's
|
|
1742
|
+
* marker — the same injection `qualify` receives, and the only way to reach the
|
|
1743
|
+
* defect channel from inside an asynchronous callback (a `throw` there runs in
|
|
1744
|
+
* its own turn, long after the executor body returned).
|
|
1745
|
+
*
|
|
1746
|
+
* `T` and `E` cannot be inferred from the body, since `settle` is a parameter.
|
|
1747
|
+
* Supply them explicitly, or let them flow from an annotated target. Absent
|
|
1748
|
+
* either, both default to `never` (Thesis #3: no path may produce `unknown` in
|
|
1749
|
+
* `E`) — so an unannotated call is a compile error at the `settle(...)` call
|
|
1750
|
+
* site, not a silently-`unknown` channel.
|
|
1751
|
+
*
|
|
1752
|
+
* An executor that never settles yields an `AsyncResult` that never resolves —
|
|
1753
|
+
* the one hazard {@link fromPromise} does not have, and identical to
|
|
1754
|
+
* `new Promise`.
|
|
1755
|
+
*
|
|
1756
|
+
* @typeParam T - the success type.
|
|
1757
|
+
* @typeParam E - the modeled error type.
|
|
1758
|
+
* @param executor - runs immediately; receives the settler and the `defect` helper.
|
|
1759
|
+
*
|
|
1760
|
+
* @category Interop
|
|
1761
|
+
*
|
|
1762
|
+
* @example
|
|
1763
|
+
* ```ts
|
|
1764
|
+
* import { fromExecutor, Err, Ok } from "unthrown";
|
|
1765
|
+
*
|
|
1766
|
+
* const listen = (port: number) =>
|
|
1767
|
+
* fromExecutor<Server, PortInUse>((settle, defect) => {
|
|
1768
|
+
* server.once("error", (cause) =>
|
|
1769
|
+
* isAddrInUse(cause) ? settle(Err(new PortInUse(port))) : settle(defect(cause)),
|
|
1770
|
+
* );
|
|
1771
|
+
* server.listen(port, () => settle(Ok(server)));
|
|
1772
|
+
* });
|
|
1773
|
+
* ```
|
|
1774
|
+
*/
|
|
1775
|
+
declare function fromExecutor<T = never, E = never>(executor: (settle: Settle<T, E>, defect: (cause: unknown) => Defect) => void): AsyncResult$1<T, E>;
|
|
1711
1776
|
/**
|
|
1712
1777
|
* The success channel of {@link all} / {@link allAsync}: a **positional tuple**
|
|
1713
1778
|
* for a fixed-length input (including the empty tuple), or a homogeneous
|
|
@@ -1790,7 +1855,10 @@ declare function allFromDict<R extends ResultRecord>(results: R): Result$1<{ [K
|
|
|
1790
1855
|
* ```ts
|
|
1791
1856
|
* import { allAsync, fromSafePromise } from "unthrown";
|
|
1792
1857
|
*
|
|
1793
|
-
* const both = allAsync([
|
|
1858
|
+
* const both = allAsync([
|
|
1859
|
+
* fromSafePromise(Promise.resolve(1)),
|
|
1860
|
+
* fromSafePromise(Promise.resolve(2)),
|
|
1861
|
+
* ]);
|
|
1794
1862
|
* (await both).get(); // => [1, 2]
|
|
1795
1863
|
* ```
|
|
1796
1864
|
*/
|
|
@@ -1877,14 +1945,15 @@ type Result<T, E> = Result$1<T, E>;
|
|
|
1877
1945
|
/**
|
|
1878
1946
|
* Companion object grouping the **`AsyncResult`-producing** entry points under
|
|
1879
1947
|
* the matching namespace: {@link AsyncResult.Ok}, {@link AsyncResult.Err},
|
|
1880
|
-
* {@link AsyncResult.Do}, {@link AsyncResult.
|
|
1881
|
-
* {@link AsyncResult.
|
|
1882
|
-
* {@link AsyncResult.allFromDict}.
|
|
1948
|
+
* {@link AsyncResult.Do}, {@link AsyncResult.fromExecutor},
|
|
1949
|
+
* {@link AsyncResult.fromPromise}, {@link AsyncResult.fromSafePromise},
|
|
1950
|
+
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict}.
|
|
1883
1951
|
*
|
|
1884
1952
|
* @remarks
|
|
1885
1953
|
* The async sibling of {@link Result}. Statics are grouped by what they
|
|
1886
|
-
* **return**, so the pre-lifted constructors, `
|
|
1887
|
-
* and the async aggregates sit here rather
|
|
1954
|
+
* **return**, so the pre-lifted constructors, `fromExecutor`,
|
|
1955
|
+
* `fromPromise`/`fromSafePromise`, and the async aggregates sit here rather
|
|
1956
|
+
* than on {@link Result}; the namespace
|
|
1888
1957
|
* already conveys "async", so the members drop the `Async` suffix their free
|
|
1889
1958
|
* functions carry (`AsyncResult.Ok` is `OkAsync`; `AsyncResult.Err` is
|
|
1890
1959
|
* `ErrAsync`; `AsyncResult.Do` is `DoAsync`; `AsyncResult.all` is `allAsync`;
|
|
@@ -1898,7 +1967,10 @@ type Result<T, E> = Result$1<T, E>;
|
|
|
1898
1967
|
* @example
|
|
1899
1968
|
* ```ts
|
|
1900
1969
|
* import { AsyncResult } from "unthrown";
|
|
1901
|
-
* const user = await AsyncResult.fromPromise(
|
|
1970
|
+
* const user = await AsyncResult.fromPromise(
|
|
1971
|
+
* fetchUser(id),
|
|
1972
|
+
* (c, defect) => defect(c),
|
|
1973
|
+
* );
|
|
1902
1974
|
* user.get(); // => the fetched user (on success)
|
|
1903
1975
|
* ```
|
|
1904
1976
|
*/
|
|
@@ -1906,6 +1978,7 @@ declare const AsyncResult: {
|
|
|
1906
1978
|
readonly Ok: typeof OkAsync;
|
|
1907
1979
|
readonly Err: typeof ErrAsync;
|
|
1908
1980
|
readonly Do: typeof DoAsync;
|
|
1981
|
+
readonly fromExecutor: typeof fromExecutor;
|
|
1909
1982
|
readonly fromPromise: typeof fromPromise;
|
|
1910
1983
|
readonly fromSafePromise: typeof fromSafePromise;
|
|
1911
1984
|
readonly all: typeof allAsync;
|
|
@@ -2034,4 +2107,4 @@ declare function TaggedError<Tag extends string>(tag: Tag, options?: {
|
|
|
2034
2107
|
readonly name?: string;
|
|
2035
2108
|
}): TaggedErrorConstructor<Tag>;
|
|
2036
2109
|
//#endregion
|
|
2037
|
-
export { type AsyncErrOf, type AsyncOkOf, AsyncResult, type AsyncResultMethods, type Awaitable, type DefectView, Do, DoAsync, Err, ErrAsync, type ErrMatcher, type ErrOf, type ErrView, type FailureView, GetError, type Matcher, NonExhaustiveError, type NotThenable, Ok, OkAsync, type OkOf, type OkView, P, type PatternMatcher, Result, type ResultMethods, TaggedError, type TaggedErrorConstructor, type TaggedErrorInstance, type UniversalPattern, all, allAsync, allFromDict, allFromDictAsync, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, match };
|
|
2110
|
+
export { type AsyncErrOf, type AsyncOkOf, AsyncResult, type AsyncResultMethods, type Awaitable, type DefectView, Do, DoAsync, Err, ErrAsync, type ErrMatcher, type ErrOf, type ErrView, type FailureView, GetError, type Matcher, NonExhaustiveError, type NotThenable, Ok, OkAsync, type OkOf, type OkView, P, type PatternMatcher, Result, type ResultMethods, type Settle, TaggedError, type TaggedErrorConstructor, type TaggedErrorInstance, type UniversalPattern, all, allAsync, allFromDict, allFromDictAsync, fromExecutor, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, match };
|
package/dist/index.mjs
CHANGED
|
@@ -537,7 +537,11 @@ function defectRes(cause) {
|
|
|
537
537
|
* // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
|
|
538
538
|
* // so the `P._` escape hatch is the only arm that can terminate the match:
|
|
539
539
|
* // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
|
|
540
|
-
* x.match({
|
|
540
|
+
* x.match({
|
|
541
|
+
* ok: () => 1,
|
|
542
|
+
* errCases: (m) => m.with(P._, () => 0),
|
|
543
|
+
* defect: () => -1,
|
|
544
|
+
* });
|
|
541
545
|
* ```
|
|
542
546
|
*
|
|
543
547
|
* @category Guards
|
|
@@ -1283,6 +1287,72 @@ function fromPromise(promise, qualify, ..._guard) {
|
|
|
1283
1287
|
function fromSafePromise(promise) {
|
|
1284
1288
|
return new AsyncRes((typeof promise === "function" ? Promise.resolve().then(promise) : Promise.resolve(promise)).then((value) => okRes(value), (cause) => defectRes(cause)));
|
|
1285
1289
|
}
|
|
1290
|
+
/**
|
|
1291
|
+
* Build an {@link AsyncResult} from a callback-style API — this library's
|
|
1292
|
+
* answer to `new Promise((resolve, reject) => …)`.
|
|
1293
|
+
*
|
|
1294
|
+
* @remarks
|
|
1295
|
+
* The settler takes a **`Result`**, not a value-or-reason pair: the caller names
|
|
1296
|
+
* the variant, so no `unknown` can enter `E` and there is no `qualify` to pass.
|
|
1297
|
+
* For a failure that is *not* modeled, settle the injected `defect` helper's
|
|
1298
|
+
* marker — the same injection `qualify` receives, and the only way to reach the
|
|
1299
|
+
* defect channel from inside an asynchronous callback (a `throw` there runs in
|
|
1300
|
+
* its own turn, long after the executor body returned).
|
|
1301
|
+
*
|
|
1302
|
+
* `T` and `E` cannot be inferred from the body, since `settle` is a parameter.
|
|
1303
|
+
* Supply them explicitly, or let them flow from an annotated target. Absent
|
|
1304
|
+
* either, both default to `never` (Thesis #3: no path may produce `unknown` in
|
|
1305
|
+
* `E`) — so an unannotated call is a compile error at the `settle(...)` call
|
|
1306
|
+
* site, not a silently-`unknown` channel.
|
|
1307
|
+
*
|
|
1308
|
+
* An executor that never settles yields an `AsyncResult` that never resolves —
|
|
1309
|
+
* the one hazard {@link fromPromise} does not have, and identical to
|
|
1310
|
+
* `new Promise`.
|
|
1311
|
+
*
|
|
1312
|
+
* @typeParam T - the success type.
|
|
1313
|
+
* @typeParam E - the modeled error type.
|
|
1314
|
+
* @param executor - runs immediately; receives the settler and the `defect` helper.
|
|
1315
|
+
*
|
|
1316
|
+
* @category Interop
|
|
1317
|
+
*
|
|
1318
|
+
* @example
|
|
1319
|
+
* ```ts
|
|
1320
|
+
* import { fromExecutor, Err, Ok } from "unthrown";
|
|
1321
|
+
*
|
|
1322
|
+
* const listen = (port: number) =>
|
|
1323
|
+
* fromExecutor<Server, PortInUse>((settle, defect) => {
|
|
1324
|
+
* server.once("error", (cause) =>
|
|
1325
|
+
* isAddrInUse(cause) ? settle(Err(new PortInUse(port))) : settle(defect(cause)),
|
|
1326
|
+
* );
|
|
1327
|
+
* server.listen(port, () => settle(Ok(server)));
|
|
1328
|
+
* });
|
|
1329
|
+
* ```
|
|
1330
|
+
*/
|
|
1331
|
+
function fromExecutor(executor) {
|
|
1332
|
+
let resolve;
|
|
1333
|
+
const promise = new Promise((r) => {
|
|
1334
|
+
resolve = r;
|
|
1335
|
+
});
|
|
1336
|
+
const settle = (result) => {
|
|
1337
|
+
if (isDefectMarker(result)) {
|
|
1338
|
+
resolve(defectRes(result.cause));
|
|
1339
|
+
return;
|
|
1340
|
+
}
|
|
1341
|
+
if (!isResult(result)) {
|
|
1342
|
+
if (isThenable(result)) Promise.resolve(result).then(void 0, () => void 0);
|
|
1343
|
+
resolve(defectRes(/* @__PURE__ */ new TypeError("unthrown: fromExecutor's settle received a non-Result value")));
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
resolve(result);
|
|
1347
|
+
};
|
|
1348
|
+
try {
|
|
1349
|
+
const returned = executor(settle, defect);
|
|
1350
|
+
if (isThenable(returned)) Promise.resolve(returned).then(void 0, (cause) => settle(defect(cause)));
|
|
1351
|
+
} catch (cause) {
|
|
1352
|
+
settle(defect(cause));
|
|
1353
|
+
}
|
|
1354
|
+
return new AsyncRes(promise);
|
|
1355
|
+
}
|
|
1286
1356
|
function qualifyToResult(cause, qualify) {
|
|
1287
1357
|
try {
|
|
1288
1358
|
const q = qualify(cause, defect);
|
|
@@ -1451,7 +1521,10 @@ function allFromDict(results) {
|
|
|
1451
1521
|
* ```ts
|
|
1452
1522
|
* import { allAsync, fromSafePromise } from "unthrown";
|
|
1453
1523
|
*
|
|
1454
|
-
* const both = allAsync([
|
|
1524
|
+
* const both = allAsync([
|
|
1525
|
+
* fromSafePromise(Promise.resolve(1)),
|
|
1526
|
+
* fromSafePromise(Promise.resolve(2)),
|
|
1527
|
+
* ]);
|
|
1455
1528
|
* (await both).get(); // => [1, 2]
|
|
1456
1529
|
* ```
|
|
1457
1530
|
*/
|
|
@@ -1534,14 +1607,15 @@ const Result = {
|
|
|
1534
1607
|
/**
|
|
1535
1608
|
* Companion object grouping the **`AsyncResult`-producing** entry points under
|
|
1536
1609
|
* the matching namespace: {@link AsyncResult.Ok}, {@link AsyncResult.Err},
|
|
1537
|
-
* {@link AsyncResult.Do}, {@link AsyncResult.
|
|
1538
|
-
* {@link AsyncResult.
|
|
1539
|
-
* {@link AsyncResult.allFromDict}.
|
|
1610
|
+
* {@link AsyncResult.Do}, {@link AsyncResult.fromExecutor},
|
|
1611
|
+
* {@link AsyncResult.fromPromise}, {@link AsyncResult.fromSafePromise},
|
|
1612
|
+
* {@link AsyncResult.all}, {@link AsyncResult.allFromDict}.
|
|
1540
1613
|
*
|
|
1541
1614
|
* @remarks
|
|
1542
1615
|
* The async sibling of {@link Result}. Statics are grouped by what they
|
|
1543
|
-
* **return**, so the pre-lifted constructors, `
|
|
1544
|
-
* and the async aggregates sit here rather
|
|
1616
|
+
* **return**, so the pre-lifted constructors, `fromExecutor`,
|
|
1617
|
+
* `fromPromise`/`fromSafePromise`, and the async aggregates sit here rather
|
|
1618
|
+
* than on {@link Result}; the namespace
|
|
1545
1619
|
* already conveys "async", so the members drop the `Async` suffix their free
|
|
1546
1620
|
* functions carry (`AsyncResult.Ok` is `OkAsync`; `AsyncResult.Err` is
|
|
1547
1621
|
* `ErrAsync`; `AsyncResult.Do` is `DoAsync`; `AsyncResult.all` is `allAsync`;
|
|
@@ -1555,7 +1629,10 @@ const Result = {
|
|
|
1555
1629
|
* @example
|
|
1556
1630
|
* ```ts
|
|
1557
1631
|
* import { AsyncResult } from "unthrown";
|
|
1558
|
-
* const user = await AsyncResult.fromPromise(
|
|
1632
|
+
* const user = await AsyncResult.fromPromise(
|
|
1633
|
+
* fetchUser(id),
|
|
1634
|
+
* (c, defect) => defect(c),
|
|
1635
|
+
* );
|
|
1559
1636
|
* user.get(); // => the fetched user (on success)
|
|
1560
1637
|
* ```
|
|
1561
1638
|
*/
|
|
@@ -1563,6 +1640,7 @@ const AsyncResult = {
|
|
|
1563
1640
|
Ok: OkAsync,
|
|
1564
1641
|
Err: ErrAsync,
|
|
1565
1642
|
Do: DoAsync,
|
|
1643
|
+
fromExecutor,
|
|
1566
1644
|
fromPromise,
|
|
1567
1645
|
fromSafePromise,
|
|
1568
1646
|
all: allAsync,
|
|
@@ -1659,4 +1737,4 @@ function TaggedError(tag, options) {
|
|
|
1659
1737
|
return TaggedErrorBase;
|
|
1660
1738
|
}
|
|
1661
1739
|
//#endregion
|
|
1662
|
-
export { AsyncResult, Do, DoAsync, Err, ErrAsync, GetError, NonExhaustiveError, Ok, OkAsync, P, Result, TaggedError, all, allAsync, allFromDict, allFromDictAsync, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, match };
|
|
1740
|
+
export { AsyncResult, Do, DoAsync, Err, ErrAsync, GetError, NonExhaustiveError, Ok, OkAsync, P, Result, TaggedError, all, allAsync, allFromDict, allFromDictAsync, fromExecutor, fromNullable, fromPromise, fromSafePromise, fromSafeThrowable, fromThrowable, isDefect, isErr, isOk, isResult, match };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unthrown",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.3.0",
|
|
4
4
|
"description": "Explicit errors as values, with a separate defect (panic) channel",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"defect",
|
|
@@ -47,13 +47,10 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@btravstack/tsconfig": "0.2.0",
|
|
50
|
-
"@
|
|
51
|
-
"@types/node": "26.1.1",
|
|
50
|
+
"@types/node": "26.1.2",
|
|
52
51
|
"@vitest/coverage-v8": "4.1.10",
|
|
53
|
-
"tsdown": "0.22.
|
|
54
|
-
"
|
|
55
|
-
"typedoc-plugin-markdown": "4.12.0",
|
|
56
|
-
"typescript": "6.0.3",
|
|
52
|
+
"tsdown": "0.22.14",
|
|
53
|
+
"typescript": "7.0.2",
|
|
57
54
|
"vitest": "4.1.10"
|
|
58
55
|
},
|
|
59
56
|
"engines": {
|
|
@@ -61,7 +58,6 @@
|
|
|
61
58
|
},
|
|
62
59
|
"scripts": {
|
|
63
60
|
"build": "tsdown src/index.ts --format cjs,esm --dts --clean",
|
|
64
|
-
"build:docs": "typedoc",
|
|
65
61
|
"dev": "tsdown src/index.ts --format cjs,esm --dts --watch",
|
|
66
62
|
"test": "vitest run",
|
|
67
63
|
"test:types": "tsc --noEmit -p tsconfig.test-d.json",
|