unthrown 5.0.0 → 5.2.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
@@ -25,7 +25,7 @@ var NonExhaustiveError = class extends Error {
25
25
  constructor(input) {
26
26
  let printed;
27
27
  try {
28
- printed = JSON.stringify(input);
28
+ printed = JSON.stringify(input) ?? String(input);
29
29
  } catch {
30
30
  printed = String(input);
31
31
  }
@@ -261,7 +261,7 @@ var Res = class {
261
261
  if (this.tag !== "Ok") return passThrough(this);
262
262
  try {
263
263
  const r = f(this.value);
264
- return isResult(r) ? r : nonResultCallbackDefect();
264
+ return isResult(r) ? r : nonResultCallbackDefect(r);
265
265
  } catch (cause) {
266
266
  return defectRes(cause);
267
267
  }
@@ -269,7 +269,7 @@ var Res = class {
269
269
  tap(f) {
270
270
  if (this.tag !== "Ok") return this;
271
271
  try {
272
- f(this.value);
272
+ silenceIfThenable(f(this.value));
273
273
  return this;
274
274
  } catch (cause) {
275
275
  return defectRes(cause);
@@ -279,7 +279,7 @@ var Res = class {
279
279
  if (this.tag !== "Ok") return this;
280
280
  try {
281
281
  const r = f(this.value);
282
- if (!isResult(r)) return nonResultCallbackDefect();
282
+ if (!isResult(r)) return nonResultCallbackDefect(r);
283
283
  return r.tag === "Ok" ? this : passThrough(r);
284
284
  } catch (cause) {
285
285
  return defectRes(cause);
@@ -289,7 +289,7 @@ var Res = class {
289
289
  if (this.tag !== "Ok") return passThrough(this);
290
290
  try {
291
291
  const r = f(this.value);
292
- if (!isResult(r)) return nonResultCallbackDefect();
292
+ if (!isResult(r)) return nonResultCallbackDefect(r);
293
293
  if (r.tag !== "Ok") return passThrough(r);
294
294
  return okRes({
295
295
  ...scopeOf(this.value),
@@ -341,7 +341,7 @@ var Res = class {
341
341
  try {
342
342
  const out = runMatch(f, this.error);
343
343
  if (isDefectMarker(out)) return defectRes(out.cause);
344
- if (!isResult(out)) return nonResultCallbackDefect();
344
+ if (!isResult(out)) return nonResultCallbackDefect(out);
345
345
  return out;
346
346
  } catch (cause) {
347
347
  return defectRes(cause);
@@ -362,6 +362,7 @@ var Res = class {
362
362
  try {
363
363
  const out = runMatch(f, this.error);
364
364
  if (isDefectMarker(out)) return observerThrowToDefect(out.cause, this.error);
365
+ silenceIfThenable(out);
365
366
  return this;
366
367
  } catch (cause) {
367
368
  return observerThrowToDefect(cause, this.error);
@@ -372,7 +373,7 @@ var Res = class {
372
373
  try {
373
374
  const r = runMatch(f, this.error);
374
375
  if (isDefectMarker(r)) return observerThrowToDefect(r.cause, this.error);
375
- if (!isResult(r)) return nonResultCallbackDefect();
376
+ if (!isResult(r)) return nonResultCallbackDefect(r);
376
377
  return r.tag === "Ok" ? this : passThrough(r);
377
378
  } catch (cause) {
378
379
  return observerThrowToDefect(cause, this.error);
@@ -382,7 +383,7 @@ var Res = class {
382
383
  if (this.tag !== "Defect") return this;
383
384
  try {
384
385
  const r = f(this.cause);
385
- return isResult(r) ? r : nonResultCallbackDefect();
386
+ return isResult(r) ? r : nonResultCallbackDefect(r);
386
387
  } catch (cause) {
387
388
  return defectRes(cause);
388
389
  }
@@ -390,7 +391,7 @@ var Res = class {
390
391
  tapDefect(f) {
391
392
  if (this.tag !== "Defect") return this;
392
393
  try {
393
- f(this.cause);
394
+ silenceIfThenable(f(this.cause));
394
395
  return this;
395
396
  } catch (cause) {
396
397
  return observerThrowToDefect(cause, this.cause);
@@ -399,7 +400,7 @@ var Res = class {
399
400
  tapFailure(f) {
400
401
  if (this.tag === "Ok") return this;
401
402
  try {
402
- f(this);
403
+ silenceIfThenable(f(this));
403
404
  return this;
404
405
  } catch (cause) {
405
406
  return observerThrowToDefect(cause, this.tag === "Err" ? this.error : this.cause);
@@ -537,7 +538,11 @@ function defectRes(cause) {
537
538
  * // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
538
539
  * // so the `P._` escape hatch is the only arm that can terminate the match:
539
540
  * // 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 });
541
+ * x.match({
542
+ * ok: () => 1,
543
+ * errCases: (m) => m.with(P._, () => 0),
544
+ * defect: () => -1,
545
+ * });
541
546
  * ```
542
547
  *
543
548
  * @category Guards
@@ -564,6 +569,30 @@ function passThrough(self) {
564
569
  return self;
565
570
  }
566
571
  /**
572
+ * Adopt-and-silence a thenable a combinator is about to **discard**.
573
+ *
574
+ * @remarks
575
+ * The observers (`tap`, `tapErrCases`, `tapDefect`, `tapFailure`) throw their
576
+ * callback's return value away, and the `Result`-returning combinators reject a
577
+ * non-`Result` one. Either way, a thenable that slipped past `NotThenable` (a
578
+ * cast, a raw-JS caller) is dropped while still in flight — and if it later
579
+ * rejects, nothing is holding it, so the rejection floats unhandled and takes
580
+ * the process down on Node by default. Worse for an observer: its whole job is
581
+ * to make a failure visible, and this is the one path where the failure is
582
+ * invisible.
583
+ *
584
+ * Adopting it costs one microtask and makes the rejection a no-op. The
585
+ * boundaries already do exactly this for a thenable `qualify` and a thenable
586
+ * `fn` (see `interop.ts`); this is the same net on the combinator side.
587
+ *
588
+ * @internal
589
+ */
590
+ function silenceIfThenable(value) {
591
+ try {
592
+ if ((typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function") Promise.resolve(value).then(void 0, () => void 0);
593
+ } catch {}
594
+ }
595
+ /**
567
596
  * The Defect minted when a callback constrained to return a `Result` returns
568
597
  * something else — reachable only from untyped/cast callers (in typed code the
569
598
  * constraint is a compile error). The combinator-side sibling of the
@@ -573,7 +602,8 @@ function passThrough(self) {
573
602
  *
574
603
  * @internal
575
604
  */
576
- function nonResultCallbackDefect() {
605
+ function nonResultCallbackDefect(returned) {
606
+ silenceIfThenable(returned);
577
607
  return defectRes(/* @__PURE__ */ new TypeError("unthrown: a combinator callback returned a non-Result value"));
578
608
  }
579
609
  /**
@@ -658,7 +688,7 @@ var AsyncRes = class AsyncRes {
658
688
  if (r.tag !== "Ok") return passThrough(r);
659
689
  try {
660
690
  const inner = await f(r.value);
661
- return isResult(inner) ? inner : nonResultCallbackDefect();
691
+ return isResult(inner) ? inner : nonResultCallbackDefect(inner);
662
692
  } catch (cause) {
663
693
  return defectRes(cause);
664
694
  }
@@ -668,7 +698,7 @@ var AsyncRes = class AsyncRes {
668
698
  return new AsyncRes(this.#promise.then((r) => {
669
699
  if (r.tag !== "Ok") return r;
670
700
  try {
671
- f(r.value);
701
+ silenceIfThenable(f(r.value));
672
702
  return r;
673
703
  } catch (cause) {
674
704
  return defectRes(cause);
@@ -680,7 +710,7 @@ var AsyncRes = class AsyncRes {
680
710
  if (r.tag !== "Ok") return passThrough(r);
681
711
  try {
682
712
  const inner = await f(r.value);
683
- if (!isResult(inner)) return nonResultCallbackDefect();
713
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
684
714
  return inner.tag === "Ok" ? r : passThrough(inner);
685
715
  } catch (cause) {
686
716
  return defectRes(cause);
@@ -692,7 +722,7 @@ var AsyncRes = class AsyncRes {
692
722
  if (r.tag !== "Ok") return passThrough(r);
693
723
  try {
694
724
  const inner = await f(r.value);
695
- if (!isResult(inner)) return nonResultCallbackDefect();
725
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
696
726
  if (inner.tag !== "Ok") return passThrough(inner);
697
727
  return okRes({
698
728
  ...scopeOf(r.value),
@@ -751,7 +781,7 @@ var AsyncRes = class AsyncRes {
751
781
  const out = runMatch(f, r.error);
752
782
  if (isDefectMarker(out)) return defectRes(out.cause);
753
783
  const inner = await out;
754
- if (!isResult(inner)) return nonResultCallbackDefect();
784
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
755
785
  return inner;
756
786
  } catch (cause) {
757
787
  return defectRes(cause);
@@ -776,6 +806,7 @@ var AsyncRes = class AsyncRes {
776
806
  try {
777
807
  const out = runMatch(f, r.error);
778
808
  if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
809
+ silenceIfThenable(out);
779
810
  return r;
780
811
  } catch (cause) {
781
812
  return observerThrowToDefect(cause, r.error);
@@ -789,7 +820,7 @@ var AsyncRes = class AsyncRes {
789
820
  const out = runMatch(f, r.error);
790
821
  if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
791
822
  const inner = await out;
792
- if (!isResult(inner)) return nonResultCallbackDefect();
823
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
793
824
  return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
794
825
  } catch (cause) {
795
826
  return observerThrowToDefect(cause, r.error);
@@ -801,7 +832,7 @@ var AsyncRes = class AsyncRes {
801
832
  if (r.tag !== "Defect") return r;
802
833
  try {
803
834
  const inner = await f(r.cause);
804
- return isResult(inner) ? inner : nonResultCallbackDefect();
835
+ return isResult(inner) ? inner : nonResultCallbackDefect(inner);
805
836
  } catch (cause) {
806
837
  return defectRes(cause);
807
838
  }
@@ -811,7 +842,7 @@ var AsyncRes = class AsyncRes {
811
842
  return new AsyncRes(this.#promise.then((r) => {
812
843
  if (r.tag !== "Defect") return r;
813
844
  try {
814
- f(r.cause);
845
+ silenceIfThenable(f(r.cause));
815
846
  return r;
816
847
  } catch (cause) {
817
848
  return observerThrowToDefect(cause, r.cause);
@@ -822,7 +853,7 @@ var AsyncRes = class AsyncRes {
822
853
  return new AsyncRes(this.#promise.then((r) => {
823
854
  if (r.tag === "Ok") return r;
824
855
  try {
825
- f(r);
856
+ silenceIfThenable(f(r));
826
857
  return r;
827
858
  } catch (cause) {
828
859
  return observerThrowToDefect(cause, r.tag === "Err" ? r.error : r.cause);
@@ -1090,6 +1121,11 @@ function fromNullable(value, onAbsent) {
1090
1121
  * — and a thenable slipped past the types at runtime becomes a `Defect` (never
1091
1122
  * an `Err(Promise)`), its orphaned rejection silenced.
1092
1123
  *
1124
+ * `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
1125
+ * already returned, so its rejection could never reach `qualify`: it becomes a
1126
+ * `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
1127
+ * than left to float. Reach for {@link fromPromise} to wrap async work.
1128
+ *
1093
1129
  * The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
1094
1130
  * `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
1095
1131
  * `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
@@ -1126,7 +1162,8 @@ function fromThrowable(fn, qualify) {
1126
1162
  const triage = qualify;
1127
1163
  return (...args) => {
1128
1164
  try {
1129
- return Ok(fn(...args));
1165
+ const value = fn(...args);
1166
+ return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
1130
1167
  } catch (cause) {
1131
1168
  return qualifyToResult(cause, triage);
1132
1169
  }
@@ -1143,6 +1180,10 @@ function fromThrowable(fn, qualify) {
1143
1180
  * `qualify`. When some throws *are* anticipated, reach for
1144
1181
  * {@link fromThrowable} and triage them.
1145
1182
  *
1183
+ * `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
1184
+ * `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
1185
+ * float. Reach for {@link fromSafePromise} to wrap async work.
1186
+ *
1146
1187
  * @typeParam A - the wrapped function's argument tuple.
1147
1188
  * @typeParam T - the wrapped function's return type.
1148
1189
  * @param fn - the throwing function to wrap.
@@ -1164,7 +1205,8 @@ function fromThrowable(fn, qualify) {
1164
1205
  function fromSafeThrowable(fn) {
1165
1206
  return (...args) => {
1166
1207
  try {
1167
- return Ok(fn(...args));
1208
+ const value = fn(...args);
1209
+ return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
1168
1210
  } catch (cause) {
1169
1211
  return defectRes(cause);
1170
1212
  }
@@ -1260,7 +1302,33 @@ function qualifyToResult(cause, qualify) {
1260
1302
  }
1261
1303
  }
1262
1304
  /**
1263
- * Runtime thenable probe for the belt-and-braces guard above. Called inside the
1305
+ * The Defect minted when a **synchronous** boundary's `fn` returns a thenable —
1306
+ * i.e. an `async` function was handed to {@link fromThrowable} /
1307
+ * {@link fromSafeThrowable}.
1308
+ *
1309
+ * @remarks
1310
+ * This is the sibling of the thenable-`qualify` net in {@link qualifyToResult},
1311
+ * and it closes a strictly worse hole. A synchronous boundary only ever sees a
1312
+ * synchronous `throw`, so an async `fn`'s rejection never reaches `qualify` at
1313
+ * all: it would sit inside `Ok(<Promise>)`, un-triaged, and then float as an
1314
+ * unhandled rejection — which terminates the process on Node by default.
1315
+ *
1316
+ * Unlike the combinator callbacks, this cannot be banned at compile time
1317
+ * without collateral damage: `T & NotThenable<T>` on `fn`'s return makes a
1318
+ * **generic** function unassignable, so `fromSafeThrowable(structuredClone)`
1319
+ * stops compiling and `T` collapses to `unknown`. (The phantom rest-tuple guard
1320
+ * `fromPromise` uses fares worse.) So the ban is enforced here, at runtime,
1321
+ * where it costs nothing: a Defect, plus adopt-and-silence so the orphaned
1322
+ * rejection cannot float.
1323
+ *
1324
+ * @internal
1325
+ */
1326
+ function thenableReturnDefect(value) {
1327
+ Promise.resolve(value).then(void 0, () => void 0);
1328
+ 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."));
1329
+ }
1330
+ /**
1331
+ * Runtime thenable probe for the belt-and-braces guards above. Called inside the
1264
1332
  * caller's `try`, so even a hostile `.then` getter lands on the Defect path.
1265
1333
  *
1266
1334
  * @internal
@@ -1388,7 +1456,10 @@ function allFromDict(results) {
1388
1456
  * ```ts
1389
1457
  * import { allAsync, fromSafePromise } from "unthrown";
1390
1458
  *
1391
- * const both = allAsync([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
1459
+ * const both = allAsync([
1460
+ * fromSafePromise(Promise.resolve(1)),
1461
+ * fromSafePromise(Promise.resolve(2)),
1462
+ * ]);
1392
1463
  * (await both).get(); // => [1, 2]
1393
1464
  * ```
1394
1465
  */
@@ -1492,7 +1563,10 @@ const Result = {
1492
1563
  * @example
1493
1564
  * ```ts
1494
1565
  * import { AsyncResult } from "unthrown";
1495
- * const user = await AsyncResult.fromPromise(fetchUser(id), (c, defect) => defect(c));
1566
+ * const user = await AsyncResult.fromPromise(
1567
+ * fetchUser(id),
1568
+ * (c, defect) => defect(c),
1569
+ * );
1496
1570
  * user.get(); // => the fetched user (on success)
1497
1571
  * ```
1498
1572
  */
package/dist/index.d.cts CHANGED
@@ -296,10 +296,17 @@ type Bound<T, K extends string, U> = Prettify<Omit<T, K> & { readonly [P in K]:
296
296
  * would escape the pipeline as an unhandled rejection instead of a `Defect`.
297
297
  * Lift async work with {@link fromPromise} and compose it with `flatMap`.
298
298
  *
299
+ * Spelled with `Extract`, not `[R] extends [PromiseLike<…>]`, so the ban also
300
+ * fires when only SOME arms of a union return are thenable — a *sometimes*-async
301
+ * callback (`flag ? 1 : work()`) is still an unawaited effect whose rejection
302
+ * the pipeline never sees. The tuple-wrapped form is false for a partial union
303
+ * and let exactly that through. This is the same reasoning `fromPromise`'s
304
+ * async-qualify guard already used.
305
+ *
299
306
  * @typeParam R - the callback's inferred return type.
300
307
  * @category Types
301
308
  */
302
- type NotThenable<R> = [R] extends [PromiseLike<unknown>] ? "unthrown: combinator callbacks are synchronous — lift async work with fromPromise and compose with flatMap" : unknown;
309
+ type NotThenable<R> = [Extract<R, PromiseLike<unknown>>] extends [never] ? unknown : "unthrown: combinator callbacks are synchronous — lift async work with fromPromise and compose with flatMap";
303
310
  /**
304
311
  * The built-in match builder over an error union `E`, as produced by
305
312
  * `match(error)`. This is what an error combinator's callback receives — chain
@@ -759,12 +766,20 @@ type ResultMethods<out T, out E> = {
759
766
  *
760
767
  * @remarks
761
768
  * A deliberate escape hatch off the errors-as-values model — it **throws the
762
- * `Err` value as-is** at the call site. Its purpose is to move a literal
763
- * `throw` behind a method, so a `no-throw` lint rule can ban raw throws while
764
- * this one sanctioned extraction remains — _not_ to replace principled
765
- * handling. When you can keep the error a value, prefer
766
- * {@link ResultMethods.match | match} / {@link ResultMethods.recoverErrCases | recoverErrCases} /
767
- * {@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.
768
783
  *
769
784
  * Type-gated as the **complement** of {@link ResultMethods.get | get}: it
770
785
  * compiles only when the error channel is **non-empty** (`E` is not `never`) —
@@ -1443,7 +1458,11 @@ declare class GetError<E = unknown> extends Error {
1443
1458
  * // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
1444
1459
  * // so the `P._` escape hatch is the only arm that can terminate the match:
1445
1460
  * // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
1446
- * 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
+ * });
1447
1466
  * ```
1448
1467
  *
1449
1468
  * @category Guards
@@ -1558,6 +1577,11 @@ declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () =>
1558
1577
  * — and a thenable slipped past the types at runtime becomes a `Defect` (never
1559
1578
  * an `Err(Promise)`), its orphaned rejection silenced.
1560
1579
  *
1580
+ * `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
1581
+ * already returned, so its rejection could never reach `qualify`: it becomes a
1582
+ * `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
1583
+ * than left to float. Reach for {@link fromPromise} to wrap async work.
1584
+ *
1561
1585
  * The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
1562
1586
  * `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
1563
1587
  * `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
@@ -1602,6 +1626,10 @@ declare function fromThrowable<A extends unknown[], T, R>(fn: (...args: A) => T,
1602
1626
  * `qualify`. When some throws *are* anticipated, reach for
1603
1627
  * {@link fromThrowable} and triage them.
1604
1628
  *
1629
+ * `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
1630
+ * `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
1631
+ * float. Reach for {@link fromSafePromise} to wrap async work.
1632
+ *
1605
1633
  * @typeParam A - the wrapped function's argument tuple.
1606
1634
  * @typeParam T - the wrapped function's return type.
1607
1635
  * @param fn - the throwing function to wrap.
@@ -1774,7 +1802,10 @@ declare function allFromDict<R extends ResultRecord>(results: R): Result$1<{ [K
1774
1802
  * ```ts
1775
1803
  * import { allAsync, fromSafePromise } from "unthrown";
1776
1804
  *
1777
- * const both = allAsync([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
1805
+ * const both = allAsync([
1806
+ * fromSafePromise(Promise.resolve(1)),
1807
+ * fromSafePromise(Promise.resolve(2)),
1808
+ * ]);
1778
1809
  * (await both).get(); // => [1, 2]
1779
1810
  * ```
1780
1811
  */
@@ -1882,7 +1913,10 @@ type Result<T, E> = Result$1<T, E>;
1882
1913
  * @example
1883
1914
  * ```ts
1884
1915
  * import { AsyncResult } from "unthrown";
1885
- * const user = await AsyncResult.fromPromise(fetchUser(id), (c, defect) => defect(c));
1916
+ * const user = await AsyncResult.fromPromise(
1917
+ * fetchUser(id),
1918
+ * (c, defect) => defect(c),
1919
+ * );
1886
1920
  * user.get(); // => the fetched user (on success)
1887
1921
  * ```
1888
1922
  */
package/dist/index.d.mts CHANGED
@@ -296,10 +296,17 @@ type Bound<T, K extends string, U> = Prettify<Omit<T, K> & { readonly [P in K]:
296
296
  * would escape the pipeline as an unhandled rejection instead of a `Defect`.
297
297
  * Lift async work with {@link fromPromise} and compose it with `flatMap`.
298
298
  *
299
+ * Spelled with `Extract`, not `[R] extends [PromiseLike<…>]`, so the ban also
300
+ * fires when only SOME arms of a union return are thenable — a *sometimes*-async
301
+ * callback (`flag ? 1 : work()`) is still an unawaited effect whose rejection
302
+ * the pipeline never sees. The tuple-wrapped form is false for a partial union
303
+ * and let exactly that through. This is the same reasoning `fromPromise`'s
304
+ * async-qualify guard already used.
305
+ *
299
306
  * @typeParam R - the callback's inferred return type.
300
307
  * @category Types
301
308
  */
302
- type NotThenable<R> = [R] extends [PromiseLike<unknown>] ? "unthrown: combinator callbacks are synchronous — lift async work with fromPromise and compose with flatMap" : unknown;
309
+ type NotThenable<R> = [Extract<R, PromiseLike<unknown>>] extends [never] ? unknown : "unthrown: combinator callbacks are synchronous — lift async work with fromPromise and compose with flatMap";
303
310
  /**
304
311
  * The built-in match builder over an error union `E`, as produced by
305
312
  * `match(error)`. This is what an error combinator's callback receives — chain
@@ -759,12 +766,20 @@ type ResultMethods<out T, out E> = {
759
766
  *
760
767
  * @remarks
761
768
  * A deliberate escape hatch off the errors-as-values model — it **throws the
762
- * `Err` value as-is** at the call site. Its purpose is to move a literal
763
- * `throw` behind a method, so a `no-throw` lint rule can ban raw throws while
764
- * this one sanctioned extraction remains — _not_ to replace principled
765
- * handling. When you can keep the error a value, prefer
766
- * {@link ResultMethods.match | match} / {@link ResultMethods.recoverErrCases | recoverErrCases} /
767
- * {@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.
768
783
  *
769
784
  * Type-gated as the **complement** of {@link ResultMethods.get | get}: it
770
785
  * compiles only when the error channel is **non-empty** (`E` is not `never`) —
@@ -1443,7 +1458,11 @@ declare class GetError<E = unknown> extends Error {
1443
1458
  * // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
1444
1459
  * // so the `P._` escape hatch is the only arm that can terminate the match:
1445
1460
  * // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
1446
- * 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
+ * });
1447
1466
  * ```
1448
1467
  *
1449
1468
  * @category Guards
@@ -1558,6 +1577,11 @@ declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () =>
1558
1577
  * — and a thenable slipped past the types at runtime becomes a `Defect` (never
1559
1578
  * an `Err(Promise)`), its orphaned rejection silenced.
1560
1579
  *
1580
+ * `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
1581
+ * already returned, so its rejection could never reach `qualify`: it becomes a
1582
+ * `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
1583
+ * than left to float. Reach for {@link fromPromise} to wrap async work.
1584
+ *
1561
1585
  * The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
1562
1586
  * `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
1563
1587
  * `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
@@ -1602,6 +1626,10 @@ declare function fromThrowable<A extends unknown[], T, R>(fn: (...args: A) => T,
1602
1626
  * `qualify`. When some throws *are* anticipated, reach for
1603
1627
  * {@link fromThrowable} and triage them.
1604
1628
  *
1629
+ * `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
1630
+ * `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
1631
+ * float. Reach for {@link fromSafePromise} to wrap async work.
1632
+ *
1605
1633
  * @typeParam A - the wrapped function's argument tuple.
1606
1634
  * @typeParam T - the wrapped function's return type.
1607
1635
  * @param fn - the throwing function to wrap.
@@ -1774,7 +1802,10 @@ declare function allFromDict<R extends ResultRecord>(results: R): Result$1<{ [K
1774
1802
  * ```ts
1775
1803
  * import { allAsync, fromSafePromise } from "unthrown";
1776
1804
  *
1777
- * const both = allAsync([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
1805
+ * const both = allAsync([
1806
+ * fromSafePromise(Promise.resolve(1)),
1807
+ * fromSafePromise(Promise.resolve(2)),
1808
+ * ]);
1778
1809
  * (await both).get(); // => [1, 2]
1779
1810
  * ```
1780
1811
  */
@@ -1882,7 +1913,10 @@ type Result<T, E> = Result$1<T, E>;
1882
1913
  * @example
1883
1914
  * ```ts
1884
1915
  * import { AsyncResult } from "unthrown";
1885
- * const user = await AsyncResult.fromPromise(fetchUser(id), (c, defect) => defect(c));
1916
+ * const user = await AsyncResult.fromPromise(
1917
+ * fetchUser(id),
1918
+ * (c, defect) => defect(c),
1919
+ * );
1886
1920
  * user.get(); // => the fetched user (on success)
1887
1921
  * ```
1888
1922
  */
package/dist/index.mjs CHANGED
@@ -24,7 +24,7 @@ var NonExhaustiveError = class extends Error {
24
24
  constructor(input) {
25
25
  let printed;
26
26
  try {
27
- printed = JSON.stringify(input);
27
+ printed = JSON.stringify(input) ?? String(input);
28
28
  } catch {
29
29
  printed = String(input);
30
30
  }
@@ -260,7 +260,7 @@ var Res = class {
260
260
  if (this.tag !== "Ok") return passThrough(this);
261
261
  try {
262
262
  const r = f(this.value);
263
- return isResult(r) ? r : nonResultCallbackDefect();
263
+ return isResult(r) ? r : nonResultCallbackDefect(r);
264
264
  } catch (cause) {
265
265
  return defectRes(cause);
266
266
  }
@@ -268,7 +268,7 @@ var Res = class {
268
268
  tap(f) {
269
269
  if (this.tag !== "Ok") return this;
270
270
  try {
271
- f(this.value);
271
+ silenceIfThenable(f(this.value));
272
272
  return this;
273
273
  } catch (cause) {
274
274
  return defectRes(cause);
@@ -278,7 +278,7 @@ var Res = class {
278
278
  if (this.tag !== "Ok") return this;
279
279
  try {
280
280
  const r = f(this.value);
281
- if (!isResult(r)) return nonResultCallbackDefect();
281
+ if (!isResult(r)) return nonResultCallbackDefect(r);
282
282
  return r.tag === "Ok" ? this : passThrough(r);
283
283
  } catch (cause) {
284
284
  return defectRes(cause);
@@ -288,7 +288,7 @@ var Res = class {
288
288
  if (this.tag !== "Ok") return passThrough(this);
289
289
  try {
290
290
  const r = f(this.value);
291
- if (!isResult(r)) return nonResultCallbackDefect();
291
+ if (!isResult(r)) return nonResultCallbackDefect(r);
292
292
  if (r.tag !== "Ok") return passThrough(r);
293
293
  return okRes({
294
294
  ...scopeOf(this.value),
@@ -340,7 +340,7 @@ var Res = class {
340
340
  try {
341
341
  const out = runMatch(f, this.error);
342
342
  if (isDefectMarker(out)) return defectRes(out.cause);
343
- if (!isResult(out)) return nonResultCallbackDefect();
343
+ if (!isResult(out)) return nonResultCallbackDefect(out);
344
344
  return out;
345
345
  } catch (cause) {
346
346
  return defectRes(cause);
@@ -361,6 +361,7 @@ var Res = class {
361
361
  try {
362
362
  const out = runMatch(f, this.error);
363
363
  if (isDefectMarker(out)) return observerThrowToDefect(out.cause, this.error);
364
+ silenceIfThenable(out);
364
365
  return this;
365
366
  } catch (cause) {
366
367
  return observerThrowToDefect(cause, this.error);
@@ -371,7 +372,7 @@ var Res = class {
371
372
  try {
372
373
  const r = runMatch(f, this.error);
373
374
  if (isDefectMarker(r)) return observerThrowToDefect(r.cause, this.error);
374
- if (!isResult(r)) return nonResultCallbackDefect();
375
+ if (!isResult(r)) return nonResultCallbackDefect(r);
375
376
  return r.tag === "Ok" ? this : passThrough(r);
376
377
  } catch (cause) {
377
378
  return observerThrowToDefect(cause, this.error);
@@ -381,7 +382,7 @@ var Res = class {
381
382
  if (this.tag !== "Defect") return this;
382
383
  try {
383
384
  const r = f(this.cause);
384
- return isResult(r) ? r : nonResultCallbackDefect();
385
+ return isResult(r) ? r : nonResultCallbackDefect(r);
385
386
  } catch (cause) {
386
387
  return defectRes(cause);
387
388
  }
@@ -389,7 +390,7 @@ var Res = class {
389
390
  tapDefect(f) {
390
391
  if (this.tag !== "Defect") return this;
391
392
  try {
392
- f(this.cause);
393
+ silenceIfThenable(f(this.cause));
393
394
  return this;
394
395
  } catch (cause) {
395
396
  return observerThrowToDefect(cause, this.cause);
@@ -398,7 +399,7 @@ var Res = class {
398
399
  tapFailure(f) {
399
400
  if (this.tag === "Ok") return this;
400
401
  try {
401
- f(this);
402
+ silenceIfThenable(f(this));
402
403
  return this;
403
404
  } catch (cause) {
404
405
  return observerThrowToDefect(cause, this.tag === "Err" ? this.error : this.cause);
@@ -536,7 +537,11 @@ function defectRes(cause) {
536
537
  * // `E` is `unknown` here — an untyped boundary has no cases to enumerate,
537
538
  * // so the `P._` escape hatch is the only arm that can terminate the match:
538
539
  * // oxlint-disable-next-line unthrown/no-catch-all-pattern -- untyped boundary: `E` is `unknown`
539
- * 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
+ * });
540
545
  * ```
541
546
  *
542
547
  * @category Guards
@@ -563,6 +568,30 @@ function passThrough(self) {
563
568
  return self;
564
569
  }
565
570
  /**
571
+ * Adopt-and-silence a thenable a combinator is about to **discard**.
572
+ *
573
+ * @remarks
574
+ * The observers (`tap`, `tapErrCases`, `tapDefect`, `tapFailure`) throw their
575
+ * callback's return value away, and the `Result`-returning combinators reject a
576
+ * non-`Result` one. Either way, a thenable that slipped past `NotThenable` (a
577
+ * cast, a raw-JS caller) is dropped while still in flight — and if it later
578
+ * rejects, nothing is holding it, so the rejection floats unhandled and takes
579
+ * the process down on Node by default. Worse for an observer: its whole job is
580
+ * to make a failure visible, and this is the one path where the failure is
581
+ * invisible.
582
+ *
583
+ * Adopting it costs one microtask and makes the rejection a no-op. The
584
+ * boundaries already do exactly this for a thenable `qualify` and a thenable
585
+ * `fn` (see `interop.ts`); this is the same net on the combinator side.
586
+ *
587
+ * @internal
588
+ */
589
+ function silenceIfThenable(value) {
590
+ try {
591
+ if ((typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function") Promise.resolve(value).then(void 0, () => void 0);
592
+ } catch {}
593
+ }
594
+ /**
566
595
  * The Defect minted when a callback constrained to return a `Result` returns
567
596
  * something else — reachable only from untyped/cast callers (in typed code the
568
597
  * constraint is a compile error). The combinator-side sibling of the
@@ -572,7 +601,8 @@ function passThrough(self) {
572
601
  *
573
602
  * @internal
574
603
  */
575
- function nonResultCallbackDefect() {
604
+ function nonResultCallbackDefect(returned) {
605
+ silenceIfThenable(returned);
576
606
  return defectRes(/* @__PURE__ */ new TypeError("unthrown: a combinator callback returned a non-Result value"));
577
607
  }
578
608
  /**
@@ -657,7 +687,7 @@ var AsyncRes = class AsyncRes {
657
687
  if (r.tag !== "Ok") return passThrough(r);
658
688
  try {
659
689
  const inner = await f(r.value);
660
- return isResult(inner) ? inner : nonResultCallbackDefect();
690
+ return isResult(inner) ? inner : nonResultCallbackDefect(inner);
661
691
  } catch (cause) {
662
692
  return defectRes(cause);
663
693
  }
@@ -667,7 +697,7 @@ var AsyncRes = class AsyncRes {
667
697
  return new AsyncRes(this.#promise.then((r) => {
668
698
  if (r.tag !== "Ok") return r;
669
699
  try {
670
- f(r.value);
700
+ silenceIfThenable(f(r.value));
671
701
  return r;
672
702
  } catch (cause) {
673
703
  return defectRes(cause);
@@ -679,7 +709,7 @@ var AsyncRes = class AsyncRes {
679
709
  if (r.tag !== "Ok") return passThrough(r);
680
710
  try {
681
711
  const inner = await f(r.value);
682
- if (!isResult(inner)) return nonResultCallbackDefect();
712
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
683
713
  return inner.tag === "Ok" ? r : passThrough(inner);
684
714
  } catch (cause) {
685
715
  return defectRes(cause);
@@ -691,7 +721,7 @@ var AsyncRes = class AsyncRes {
691
721
  if (r.tag !== "Ok") return passThrough(r);
692
722
  try {
693
723
  const inner = await f(r.value);
694
- if (!isResult(inner)) return nonResultCallbackDefect();
724
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
695
725
  if (inner.tag !== "Ok") return passThrough(inner);
696
726
  return okRes({
697
727
  ...scopeOf(r.value),
@@ -750,7 +780,7 @@ var AsyncRes = class AsyncRes {
750
780
  const out = runMatch(f, r.error);
751
781
  if (isDefectMarker(out)) return defectRes(out.cause);
752
782
  const inner = await out;
753
- if (!isResult(inner)) return nonResultCallbackDefect();
783
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
754
784
  return inner;
755
785
  } catch (cause) {
756
786
  return defectRes(cause);
@@ -775,6 +805,7 @@ var AsyncRes = class AsyncRes {
775
805
  try {
776
806
  const out = runMatch(f, r.error);
777
807
  if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
808
+ silenceIfThenable(out);
778
809
  return r;
779
810
  } catch (cause) {
780
811
  return observerThrowToDefect(cause, r.error);
@@ -788,7 +819,7 @@ var AsyncRes = class AsyncRes {
788
819
  const out = runMatch(f, r.error);
789
820
  if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
790
821
  const inner = await out;
791
- if (!isResult(inner)) return nonResultCallbackDefect();
822
+ if (!isResult(inner)) return nonResultCallbackDefect(inner);
792
823
  return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
793
824
  } catch (cause) {
794
825
  return observerThrowToDefect(cause, r.error);
@@ -800,7 +831,7 @@ var AsyncRes = class AsyncRes {
800
831
  if (r.tag !== "Defect") return r;
801
832
  try {
802
833
  const inner = await f(r.cause);
803
- return isResult(inner) ? inner : nonResultCallbackDefect();
834
+ return isResult(inner) ? inner : nonResultCallbackDefect(inner);
804
835
  } catch (cause) {
805
836
  return defectRes(cause);
806
837
  }
@@ -810,7 +841,7 @@ var AsyncRes = class AsyncRes {
810
841
  return new AsyncRes(this.#promise.then((r) => {
811
842
  if (r.tag !== "Defect") return r;
812
843
  try {
813
- f(r.cause);
844
+ silenceIfThenable(f(r.cause));
814
845
  return r;
815
846
  } catch (cause) {
816
847
  return observerThrowToDefect(cause, r.cause);
@@ -821,7 +852,7 @@ var AsyncRes = class AsyncRes {
821
852
  return new AsyncRes(this.#promise.then((r) => {
822
853
  if (r.tag === "Ok") return r;
823
854
  try {
824
- f(r);
855
+ silenceIfThenable(f(r));
825
856
  return r;
826
857
  } catch (cause) {
827
858
  return observerThrowToDefect(cause, r.tag === "Err" ? r.error : r.cause);
@@ -1089,6 +1120,11 @@ function fromNullable(value, onAbsent) {
1089
1120
  * — and a thenable slipped past the types at runtime becomes a `Defect` (never
1090
1121
  * an `Err(Promise)`), its orphaned rejection silenced.
1091
1122
  *
1123
+ * `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
1124
+ * already returned, so its rejection could never reach `qualify`: it becomes a
1125
+ * `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
1126
+ * than left to float. Reach for {@link fromPromise} to wrap async work.
1127
+ *
1092
1128
  * The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
1093
1129
  * `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
1094
1130
  * `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
@@ -1125,7 +1161,8 @@ function fromThrowable(fn, qualify) {
1125
1161
  const triage = qualify;
1126
1162
  return (...args) => {
1127
1163
  try {
1128
- return Ok(fn(...args));
1164
+ const value = fn(...args);
1165
+ return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
1129
1166
  } catch (cause) {
1130
1167
  return qualifyToResult(cause, triage);
1131
1168
  }
@@ -1142,6 +1179,10 @@ function fromThrowable(fn, qualify) {
1142
1179
  * `qualify`. When some throws *are* anticipated, reach for
1143
1180
  * {@link fromThrowable} and triage them.
1144
1181
  *
1182
+ * `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
1183
+ * `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
1184
+ * float. Reach for {@link fromSafePromise} to wrap async work.
1185
+ *
1145
1186
  * @typeParam A - the wrapped function's argument tuple.
1146
1187
  * @typeParam T - the wrapped function's return type.
1147
1188
  * @param fn - the throwing function to wrap.
@@ -1163,7 +1204,8 @@ function fromThrowable(fn, qualify) {
1163
1204
  function fromSafeThrowable(fn) {
1164
1205
  return (...args) => {
1165
1206
  try {
1166
- return Ok(fn(...args));
1207
+ const value = fn(...args);
1208
+ return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
1167
1209
  } catch (cause) {
1168
1210
  return defectRes(cause);
1169
1211
  }
@@ -1259,7 +1301,33 @@ function qualifyToResult(cause, qualify) {
1259
1301
  }
1260
1302
  }
1261
1303
  /**
1262
- * Runtime thenable probe for the belt-and-braces guard above. Called inside the
1304
+ * The Defect minted when a **synchronous** boundary's `fn` returns a thenable —
1305
+ * i.e. an `async` function was handed to {@link fromThrowable} /
1306
+ * {@link fromSafeThrowable}.
1307
+ *
1308
+ * @remarks
1309
+ * This is the sibling of the thenable-`qualify` net in {@link qualifyToResult},
1310
+ * and it closes a strictly worse hole. A synchronous boundary only ever sees a
1311
+ * synchronous `throw`, so an async `fn`'s rejection never reaches `qualify` at
1312
+ * all: it would sit inside `Ok(<Promise>)`, un-triaged, and then float as an
1313
+ * unhandled rejection — which terminates the process on Node by default.
1314
+ *
1315
+ * Unlike the combinator callbacks, this cannot be banned at compile time
1316
+ * without collateral damage: `T & NotThenable<T>` on `fn`'s return makes a
1317
+ * **generic** function unassignable, so `fromSafeThrowable(structuredClone)`
1318
+ * stops compiling and `T` collapses to `unknown`. (The phantom rest-tuple guard
1319
+ * `fromPromise` uses fares worse.) So the ban is enforced here, at runtime,
1320
+ * where it costs nothing: a Defect, plus adopt-and-silence so the orphaned
1321
+ * rejection cannot float.
1322
+ *
1323
+ * @internal
1324
+ */
1325
+ function thenableReturnDefect(value) {
1326
+ Promise.resolve(value).then(void 0, () => void 0);
1327
+ 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."));
1328
+ }
1329
+ /**
1330
+ * Runtime thenable probe for the belt-and-braces guards above. Called inside the
1263
1331
  * caller's `try`, so even a hostile `.then` getter lands on the Defect path.
1264
1332
  *
1265
1333
  * @internal
@@ -1387,7 +1455,10 @@ function allFromDict(results) {
1387
1455
  * ```ts
1388
1456
  * import { allAsync, fromSafePromise } from "unthrown";
1389
1457
  *
1390
- * const both = allAsync([fromSafePromise(Promise.resolve(1)), fromSafePromise(Promise.resolve(2))]);
1458
+ * const both = allAsync([
1459
+ * fromSafePromise(Promise.resolve(1)),
1460
+ * fromSafePromise(Promise.resolve(2)),
1461
+ * ]);
1391
1462
  * (await both).get(); // => [1, 2]
1392
1463
  * ```
1393
1464
  */
@@ -1491,7 +1562,10 @@ const Result = {
1491
1562
  * @example
1492
1563
  * ```ts
1493
1564
  * import { AsyncResult } from "unthrown";
1494
- * const user = await AsyncResult.fromPromise(fetchUser(id), (c, defect) => defect(c));
1565
+ * const user = await AsyncResult.fromPromise(
1566
+ * fetchUser(id),
1567
+ * (c, defect) => defect(c),
1568
+ * );
1495
1569
  * user.get(); // => the fetched user (on success)
1496
1570
  * ```
1497
1571
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unthrown",
3
- "version": "5.0.0",
3
+ "version": "5.2.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",