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 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({ ok: () => 1, errCases: (m) => m.with(P._, () => 0), defect: () => -1 });
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([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
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.fromPromise},
1539
- * {@link AsyncResult.fromSafePromise}, {@link AsyncResult.all},
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, `fromPromise`/`fromSafePromise`,
1545
- * and the async aggregates sit here rather than on {@link Result}; the namespace
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(fetchUser(id), (c, defect) => defect(c));
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. Its purpose is to move a literal
770
- * `throw` behind a method, so a `no-throw` lint rule can ban raw throws while
771
- * this one sanctioned extraction remains — _not_ to replace principled
772
- * handling. When you can keep the error a value, prefer
773
- * {@link ResultMethods.match | match} / {@link ResultMethods.recoverErrCases | recoverErrCases} /
774
- * {@link ResultMethods.flatMapErrCases | flatMapErrCases}.
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({ ok: () => 1, errCases: (m) => m.with(P._, () => 0), defect: () => -1 });
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([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
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.fromPromise},
1881
- * {@link AsyncResult.fromSafePromise}, {@link AsyncResult.all},
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, `fromPromise`/`fromSafePromise`,
1887
- * and the async aggregates sit here rather than on {@link Result}; the namespace
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(fetchUser(id), (c, defect) => defect(c));
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. Its purpose is to move a literal
770
- * `throw` behind a method, so a `no-throw` lint rule can ban raw throws while
771
- * this one sanctioned extraction remains — _not_ to replace principled
772
- * handling. When you can keep the error a value, prefer
773
- * {@link ResultMethods.match | match} / {@link ResultMethods.recoverErrCases | recoverErrCases} /
774
- * {@link ResultMethods.flatMapErrCases | flatMapErrCases}.
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({ ok: () => 1, errCases: (m) => m.with(P._, () => 0), defect: () => -1 });
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([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
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.fromPromise},
1881
- * {@link AsyncResult.fromSafePromise}, {@link AsyncResult.all},
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, `fromPromise`/`fromSafePromise`,
1887
- * and the async aggregates sit here rather than on {@link Result}; the namespace
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(fetchUser(id), (c, defect) => defect(c));
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({ ok: () => 1, errCases: (m) => m.with(P._, () => 0), defect: () => -1 });
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([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
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.fromPromise},
1538
- * {@link AsyncResult.fromSafePromise}, {@link AsyncResult.all},
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, `fromPromise`/`fromSafePromise`,
1544
- * and the async aggregates sit here rather than on {@link Result}; the namespace
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(fetchUser(id), (c, defect) => defect(c));
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.1.0",
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
- "@btravstack/typedoc": "0.1.0",
51
- "@types/node": "26.1.1",
50
+ "@types/node": "26.1.2",
52
51
  "@vitest/coverage-v8": "4.1.10",
53
- "tsdown": "0.22.12",
54
- "typedoc": "0.28.20",
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",