unthrown 5.0.0 → 5.1.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 -23
- package/dist/index.d.cts +17 -1
- package/dist/index.d.mts +17 -1
- package/dist/index.mjs +87 -23
- package/package.json +1 -1
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);
|
|
@@ -564,6 +565,30 @@ function passThrough(self) {
|
|
|
564
565
|
return self;
|
|
565
566
|
}
|
|
566
567
|
/**
|
|
568
|
+
* Adopt-and-silence a thenable a combinator is about to **discard**.
|
|
569
|
+
*
|
|
570
|
+
* @remarks
|
|
571
|
+
* The observers (`tap`, `tapErrCases`, `tapDefect`, `tapFailure`) throw their
|
|
572
|
+
* callback's return value away, and the `Result`-returning combinators reject a
|
|
573
|
+
* non-`Result` one. Either way, a thenable that slipped past `NotThenable` (a
|
|
574
|
+
* cast, a raw-JS caller) is dropped while still in flight — and if it later
|
|
575
|
+
* rejects, nothing is holding it, so the rejection floats unhandled and takes
|
|
576
|
+
* the process down on Node by default. Worse for an observer: its whole job is
|
|
577
|
+
* to make a failure visible, and this is the one path where the failure is
|
|
578
|
+
* invisible.
|
|
579
|
+
*
|
|
580
|
+
* Adopting it costs one microtask and makes the rejection a no-op. The
|
|
581
|
+
* boundaries already do exactly this for a thenable `qualify` and a thenable
|
|
582
|
+
* `fn` (see `interop.ts`); this is the same net on the combinator side.
|
|
583
|
+
*
|
|
584
|
+
* @internal
|
|
585
|
+
*/
|
|
586
|
+
function silenceIfThenable(value) {
|
|
587
|
+
try {
|
|
588
|
+
if ((typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function") Promise.resolve(value).then(void 0, () => void 0);
|
|
589
|
+
} catch {}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
567
592
|
* The Defect minted when a callback constrained to return a `Result` returns
|
|
568
593
|
* something else — reachable only from untyped/cast callers (in typed code the
|
|
569
594
|
* constraint is a compile error). The combinator-side sibling of the
|
|
@@ -573,7 +598,8 @@ function passThrough(self) {
|
|
|
573
598
|
*
|
|
574
599
|
* @internal
|
|
575
600
|
*/
|
|
576
|
-
function nonResultCallbackDefect() {
|
|
601
|
+
function nonResultCallbackDefect(returned) {
|
|
602
|
+
silenceIfThenable(returned);
|
|
577
603
|
return defectRes(/* @__PURE__ */ new TypeError("unthrown: a combinator callback returned a non-Result value"));
|
|
578
604
|
}
|
|
579
605
|
/**
|
|
@@ -658,7 +684,7 @@ var AsyncRes = class AsyncRes {
|
|
|
658
684
|
if (r.tag !== "Ok") return passThrough(r);
|
|
659
685
|
try {
|
|
660
686
|
const inner = await f(r.value);
|
|
661
|
-
return isResult(inner) ? inner : nonResultCallbackDefect();
|
|
687
|
+
return isResult(inner) ? inner : nonResultCallbackDefect(inner);
|
|
662
688
|
} catch (cause) {
|
|
663
689
|
return defectRes(cause);
|
|
664
690
|
}
|
|
@@ -668,7 +694,7 @@ var AsyncRes = class AsyncRes {
|
|
|
668
694
|
return new AsyncRes(this.#promise.then((r) => {
|
|
669
695
|
if (r.tag !== "Ok") return r;
|
|
670
696
|
try {
|
|
671
|
-
f(r.value);
|
|
697
|
+
silenceIfThenable(f(r.value));
|
|
672
698
|
return r;
|
|
673
699
|
} catch (cause) {
|
|
674
700
|
return defectRes(cause);
|
|
@@ -680,7 +706,7 @@ var AsyncRes = class AsyncRes {
|
|
|
680
706
|
if (r.tag !== "Ok") return passThrough(r);
|
|
681
707
|
try {
|
|
682
708
|
const inner = await f(r.value);
|
|
683
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
709
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
684
710
|
return inner.tag === "Ok" ? r : passThrough(inner);
|
|
685
711
|
} catch (cause) {
|
|
686
712
|
return defectRes(cause);
|
|
@@ -692,7 +718,7 @@ var AsyncRes = class AsyncRes {
|
|
|
692
718
|
if (r.tag !== "Ok") return passThrough(r);
|
|
693
719
|
try {
|
|
694
720
|
const inner = await f(r.value);
|
|
695
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
721
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
696
722
|
if (inner.tag !== "Ok") return passThrough(inner);
|
|
697
723
|
return okRes({
|
|
698
724
|
...scopeOf(r.value),
|
|
@@ -751,7 +777,7 @@ var AsyncRes = class AsyncRes {
|
|
|
751
777
|
const out = runMatch(f, r.error);
|
|
752
778
|
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
753
779
|
const inner = await out;
|
|
754
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
780
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
755
781
|
return inner;
|
|
756
782
|
} catch (cause) {
|
|
757
783
|
return defectRes(cause);
|
|
@@ -776,6 +802,7 @@ var AsyncRes = class AsyncRes {
|
|
|
776
802
|
try {
|
|
777
803
|
const out = runMatch(f, r.error);
|
|
778
804
|
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
805
|
+
silenceIfThenable(out);
|
|
779
806
|
return r;
|
|
780
807
|
} catch (cause) {
|
|
781
808
|
return observerThrowToDefect(cause, r.error);
|
|
@@ -789,7 +816,7 @@ var AsyncRes = class AsyncRes {
|
|
|
789
816
|
const out = runMatch(f, r.error);
|
|
790
817
|
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
791
818
|
const inner = await out;
|
|
792
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
819
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
793
820
|
return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
|
|
794
821
|
} catch (cause) {
|
|
795
822
|
return observerThrowToDefect(cause, r.error);
|
|
@@ -801,7 +828,7 @@ var AsyncRes = class AsyncRes {
|
|
|
801
828
|
if (r.tag !== "Defect") return r;
|
|
802
829
|
try {
|
|
803
830
|
const inner = await f(r.cause);
|
|
804
|
-
return isResult(inner) ? inner : nonResultCallbackDefect();
|
|
831
|
+
return isResult(inner) ? inner : nonResultCallbackDefect(inner);
|
|
805
832
|
} catch (cause) {
|
|
806
833
|
return defectRes(cause);
|
|
807
834
|
}
|
|
@@ -811,7 +838,7 @@ var AsyncRes = class AsyncRes {
|
|
|
811
838
|
return new AsyncRes(this.#promise.then((r) => {
|
|
812
839
|
if (r.tag !== "Defect") return r;
|
|
813
840
|
try {
|
|
814
|
-
f(r.cause);
|
|
841
|
+
silenceIfThenable(f(r.cause));
|
|
815
842
|
return r;
|
|
816
843
|
} catch (cause) {
|
|
817
844
|
return observerThrowToDefect(cause, r.cause);
|
|
@@ -822,7 +849,7 @@ var AsyncRes = class AsyncRes {
|
|
|
822
849
|
return new AsyncRes(this.#promise.then((r) => {
|
|
823
850
|
if (r.tag === "Ok") return r;
|
|
824
851
|
try {
|
|
825
|
-
f(r);
|
|
852
|
+
silenceIfThenable(f(r));
|
|
826
853
|
return r;
|
|
827
854
|
} catch (cause) {
|
|
828
855
|
return observerThrowToDefect(cause, r.tag === "Err" ? r.error : r.cause);
|
|
@@ -1090,6 +1117,11 @@ function fromNullable(value, onAbsent) {
|
|
|
1090
1117
|
* — and a thenable slipped past the types at runtime becomes a `Defect` (never
|
|
1091
1118
|
* an `Err(Promise)`), its orphaned rejection silenced.
|
|
1092
1119
|
*
|
|
1120
|
+
* `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
|
|
1121
|
+
* already returned, so its rejection could never reach `qualify`: it becomes a
|
|
1122
|
+
* `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
|
|
1123
|
+
* than left to float. Reach for {@link fromPromise} to wrap async work.
|
|
1124
|
+
*
|
|
1093
1125
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
1094
1126
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
1095
1127
|
* `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
|
|
@@ -1126,7 +1158,8 @@ function fromThrowable(fn, qualify) {
|
|
|
1126
1158
|
const triage = qualify;
|
|
1127
1159
|
return (...args) => {
|
|
1128
1160
|
try {
|
|
1129
|
-
|
|
1161
|
+
const value = fn(...args);
|
|
1162
|
+
return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
|
|
1130
1163
|
} catch (cause) {
|
|
1131
1164
|
return qualifyToResult(cause, triage);
|
|
1132
1165
|
}
|
|
@@ -1143,6 +1176,10 @@ function fromThrowable(fn, qualify) {
|
|
|
1143
1176
|
* `qualify`. When some throws *are* anticipated, reach for
|
|
1144
1177
|
* {@link fromThrowable} and triage them.
|
|
1145
1178
|
*
|
|
1179
|
+
* `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
|
|
1180
|
+
* `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
|
|
1181
|
+
* float. Reach for {@link fromSafePromise} to wrap async work.
|
|
1182
|
+
*
|
|
1146
1183
|
* @typeParam A - the wrapped function's argument tuple.
|
|
1147
1184
|
* @typeParam T - the wrapped function's return type.
|
|
1148
1185
|
* @param fn - the throwing function to wrap.
|
|
@@ -1164,7 +1201,8 @@ function fromThrowable(fn, qualify) {
|
|
|
1164
1201
|
function fromSafeThrowable(fn) {
|
|
1165
1202
|
return (...args) => {
|
|
1166
1203
|
try {
|
|
1167
|
-
|
|
1204
|
+
const value = fn(...args);
|
|
1205
|
+
return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
|
|
1168
1206
|
} catch (cause) {
|
|
1169
1207
|
return defectRes(cause);
|
|
1170
1208
|
}
|
|
@@ -1260,7 +1298,33 @@ function qualifyToResult(cause, qualify) {
|
|
|
1260
1298
|
}
|
|
1261
1299
|
}
|
|
1262
1300
|
/**
|
|
1263
|
-
*
|
|
1301
|
+
* The Defect minted when a **synchronous** boundary's `fn` returns a thenable —
|
|
1302
|
+
* i.e. an `async` function was handed to {@link fromThrowable} /
|
|
1303
|
+
* {@link fromSafeThrowable}.
|
|
1304
|
+
*
|
|
1305
|
+
* @remarks
|
|
1306
|
+
* This is the sibling of the thenable-`qualify` net in {@link qualifyToResult},
|
|
1307
|
+
* and it closes a strictly worse hole. A synchronous boundary only ever sees a
|
|
1308
|
+
* synchronous `throw`, so an async `fn`'s rejection never reaches `qualify` at
|
|
1309
|
+
* all: it would sit inside `Ok(<Promise>)`, un-triaged, and then float as an
|
|
1310
|
+
* unhandled rejection — which terminates the process on Node by default.
|
|
1311
|
+
*
|
|
1312
|
+
* Unlike the combinator callbacks, this cannot be banned at compile time
|
|
1313
|
+
* without collateral damage: `T & NotThenable<T>` on `fn`'s return makes a
|
|
1314
|
+
* **generic** function unassignable, so `fromSafeThrowable(structuredClone)`
|
|
1315
|
+
* stops compiling and `T` collapses to `unknown`. (The phantom rest-tuple guard
|
|
1316
|
+
* `fromPromise` uses fares worse.) So the ban is enforced here, at runtime,
|
|
1317
|
+
* where it costs nothing: a Defect, plus adopt-and-silence so the orphaned
|
|
1318
|
+
* rejection cannot float.
|
|
1319
|
+
*
|
|
1320
|
+
* @internal
|
|
1321
|
+
*/
|
|
1322
|
+
function thenableReturnDefect(value) {
|
|
1323
|
+
Promise.resolve(value).then(void 0, () => void 0);
|
|
1324
|
+
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."));
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Runtime thenable probe for the belt-and-braces guards above. Called inside the
|
|
1264
1328
|
* caller's `try`, so even a hostile `.then` getter lands on the Defect path.
|
|
1265
1329
|
*
|
|
1266
1330
|
* @internal
|
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 [
|
|
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
|
|
@@ -1558,6 +1565,11 @@ declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () =>
|
|
|
1558
1565
|
* — and a thenable slipped past the types at runtime becomes a `Defect` (never
|
|
1559
1566
|
* an `Err(Promise)`), its orphaned rejection silenced.
|
|
1560
1567
|
*
|
|
1568
|
+
* `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
|
|
1569
|
+
* already returned, so its rejection could never reach `qualify`: it becomes a
|
|
1570
|
+
* `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
|
|
1571
|
+
* than left to float. Reach for {@link fromPromise} to wrap async work.
|
|
1572
|
+
*
|
|
1561
1573
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
1562
1574
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
1563
1575
|
* `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
|
|
@@ -1602,6 +1614,10 @@ declare function fromThrowable<A extends unknown[], T, R>(fn: (...args: A) => T,
|
|
|
1602
1614
|
* `qualify`. When some throws *are* anticipated, reach for
|
|
1603
1615
|
* {@link fromThrowable} and triage them.
|
|
1604
1616
|
*
|
|
1617
|
+
* `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
|
|
1618
|
+
* `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
|
|
1619
|
+
* float. Reach for {@link fromSafePromise} to wrap async work.
|
|
1620
|
+
*
|
|
1605
1621
|
* @typeParam A - the wrapped function's argument tuple.
|
|
1606
1622
|
* @typeParam T - the wrapped function's return type.
|
|
1607
1623
|
* @param fn - the throwing function to wrap.
|
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 [
|
|
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
|
|
@@ -1558,6 +1565,11 @@ declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () =>
|
|
|
1558
1565
|
* — and a thenable slipped past the types at runtime becomes a `Defect` (never
|
|
1559
1566
|
* an `Err(Promise)`), its orphaned rejection silenced.
|
|
1560
1567
|
*
|
|
1568
|
+
* `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
|
|
1569
|
+
* already returned, so its rejection could never reach `qualify`: it becomes a
|
|
1570
|
+
* `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
|
|
1571
|
+
* than left to float. Reach for {@link fromPromise} to wrap async work.
|
|
1572
|
+
*
|
|
1561
1573
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
1562
1574
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
1563
1575
|
* `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
|
|
@@ -1602,6 +1614,10 @@ declare function fromThrowable<A extends unknown[], T, R>(fn: (...args: A) => T,
|
|
|
1602
1614
|
* `qualify`. When some throws *are* anticipated, reach for
|
|
1603
1615
|
* {@link fromThrowable} and triage them.
|
|
1604
1616
|
*
|
|
1617
|
+
* `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
|
|
1618
|
+
* `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
|
|
1619
|
+
* float. Reach for {@link fromSafePromise} to wrap async work.
|
|
1620
|
+
*
|
|
1605
1621
|
* @typeParam A - the wrapped function's argument tuple.
|
|
1606
1622
|
* @typeParam T - the wrapped function's return type.
|
|
1607
1623
|
* @param fn - the throwing function to wrap.
|
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);
|
|
@@ -563,6 +564,30 @@ function passThrough(self) {
|
|
|
563
564
|
return self;
|
|
564
565
|
}
|
|
565
566
|
/**
|
|
567
|
+
* Adopt-and-silence a thenable a combinator is about to **discard**.
|
|
568
|
+
*
|
|
569
|
+
* @remarks
|
|
570
|
+
* The observers (`tap`, `tapErrCases`, `tapDefect`, `tapFailure`) throw their
|
|
571
|
+
* callback's return value away, and the `Result`-returning combinators reject a
|
|
572
|
+
* non-`Result` one. Either way, a thenable that slipped past `NotThenable` (a
|
|
573
|
+
* cast, a raw-JS caller) is dropped while still in flight — and if it later
|
|
574
|
+
* rejects, nothing is holding it, so the rejection floats unhandled and takes
|
|
575
|
+
* the process down on Node by default. Worse for an observer: its whole job is
|
|
576
|
+
* to make a failure visible, and this is the one path where the failure is
|
|
577
|
+
* invisible.
|
|
578
|
+
*
|
|
579
|
+
* Adopting it costs one microtask and makes the rejection a no-op. The
|
|
580
|
+
* boundaries already do exactly this for a thenable `qualify` and a thenable
|
|
581
|
+
* `fn` (see `interop.ts`); this is the same net on the combinator side.
|
|
582
|
+
*
|
|
583
|
+
* @internal
|
|
584
|
+
*/
|
|
585
|
+
function silenceIfThenable(value) {
|
|
586
|
+
try {
|
|
587
|
+
if ((typeof value === "object" || typeof value === "function") && value !== null && typeof value.then === "function") Promise.resolve(value).then(void 0, () => void 0);
|
|
588
|
+
} catch {}
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
566
591
|
* The Defect minted when a callback constrained to return a `Result` returns
|
|
567
592
|
* something else — reachable only from untyped/cast callers (in typed code the
|
|
568
593
|
* constraint is a compile error). The combinator-side sibling of the
|
|
@@ -572,7 +597,8 @@ function passThrough(self) {
|
|
|
572
597
|
*
|
|
573
598
|
* @internal
|
|
574
599
|
*/
|
|
575
|
-
function nonResultCallbackDefect() {
|
|
600
|
+
function nonResultCallbackDefect(returned) {
|
|
601
|
+
silenceIfThenable(returned);
|
|
576
602
|
return defectRes(/* @__PURE__ */ new TypeError("unthrown: a combinator callback returned a non-Result value"));
|
|
577
603
|
}
|
|
578
604
|
/**
|
|
@@ -657,7 +683,7 @@ var AsyncRes = class AsyncRes {
|
|
|
657
683
|
if (r.tag !== "Ok") return passThrough(r);
|
|
658
684
|
try {
|
|
659
685
|
const inner = await f(r.value);
|
|
660
|
-
return isResult(inner) ? inner : nonResultCallbackDefect();
|
|
686
|
+
return isResult(inner) ? inner : nonResultCallbackDefect(inner);
|
|
661
687
|
} catch (cause) {
|
|
662
688
|
return defectRes(cause);
|
|
663
689
|
}
|
|
@@ -667,7 +693,7 @@ var AsyncRes = class AsyncRes {
|
|
|
667
693
|
return new AsyncRes(this.#promise.then((r) => {
|
|
668
694
|
if (r.tag !== "Ok") return r;
|
|
669
695
|
try {
|
|
670
|
-
f(r.value);
|
|
696
|
+
silenceIfThenable(f(r.value));
|
|
671
697
|
return r;
|
|
672
698
|
} catch (cause) {
|
|
673
699
|
return defectRes(cause);
|
|
@@ -679,7 +705,7 @@ var AsyncRes = class AsyncRes {
|
|
|
679
705
|
if (r.tag !== "Ok") return passThrough(r);
|
|
680
706
|
try {
|
|
681
707
|
const inner = await f(r.value);
|
|
682
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
708
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
683
709
|
return inner.tag === "Ok" ? r : passThrough(inner);
|
|
684
710
|
} catch (cause) {
|
|
685
711
|
return defectRes(cause);
|
|
@@ -691,7 +717,7 @@ var AsyncRes = class AsyncRes {
|
|
|
691
717
|
if (r.tag !== "Ok") return passThrough(r);
|
|
692
718
|
try {
|
|
693
719
|
const inner = await f(r.value);
|
|
694
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
720
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
695
721
|
if (inner.tag !== "Ok") return passThrough(inner);
|
|
696
722
|
return okRes({
|
|
697
723
|
...scopeOf(r.value),
|
|
@@ -750,7 +776,7 @@ var AsyncRes = class AsyncRes {
|
|
|
750
776
|
const out = runMatch(f, r.error);
|
|
751
777
|
if (isDefectMarker(out)) return defectRes(out.cause);
|
|
752
778
|
const inner = await out;
|
|
753
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
779
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
754
780
|
return inner;
|
|
755
781
|
} catch (cause) {
|
|
756
782
|
return defectRes(cause);
|
|
@@ -775,6 +801,7 @@ var AsyncRes = class AsyncRes {
|
|
|
775
801
|
try {
|
|
776
802
|
const out = runMatch(f, r.error);
|
|
777
803
|
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
804
|
+
silenceIfThenable(out);
|
|
778
805
|
return r;
|
|
779
806
|
} catch (cause) {
|
|
780
807
|
return observerThrowToDefect(cause, r.error);
|
|
@@ -788,7 +815,7 @@ var AsyncRes = class AsyncRes {
|
|
|
788
815
|
const out = runMatch(f, r.error);
|
|
789
816
|
if (isDefectMarker(out)) return observerThrowToDefect(out.cause, r.error);
|
|
790
817
|
const inner = await out;
|
|
791
|
-
if (!isResult(inner)) return nonResultCallbackDefect();
|
|
818
|
+
if (!isResult(inner)) return nonResultCallbackDefect(inner);
|
|
792
819
|
return inner.tag === "Ok" ? passThrough(r) : passThrough(inner);
|
|
793
820
|
} catch (cause) {
|
|
794
821
|
return observerThrowToDefect(cause, r.error);
|
|
@@ -800,7 +827,7 @@ var AsyncRes = class AsyncRes {
|
|
|
800
827
|
if (r.tag !== "Defect") return r;
|
|
801
828
|
try {
|
|
802
829
|
const inner = await f(r.cause);
|
|
803
|
-
return isResult(inner) ? inner : nonResultCallbackDefect();
|
|
830
|
+
return isResult(inner) ? inner : nonResultCallbackDefect(inner);
|
|
804
831
|
} catch (cause) {
|
|
805
832
|
return defectRes(cause);
|
|
806
833
|
}
|
|
@@ -810,7 +837,7 @@ var AsyncRes = class AsyncRes {
|
|
|
810
837
|
return new AsyncRes(this.#promise.then((r) => {
|
|
811
838
|
if (r.tag !== "Defect") return r;
|
|
812
839
|
try {
|
|
813
|
-
f(r.cause);
|
|
840
|
+
silenceIfThenable(f(r.cause));
|
|
814
841
|
return r;
|
|
815
842
|
} catch (cause) {
|
|
816
843
|
return observerThrowToDefect(cause, r.cause);
|
|
@@ -821,7 +848,7 @@ var AsyncRes = class AsyncRes {
|
|
|
821
848
|
return new AsyncRes(this.#promise.then((r) => {
|
|
822
849
|
if (r.tag === "Ok") return r;
|
|
823
850
|
try {
|
|
824
|
-
f(r);
|
|
851
|
+
silenceIfThenable(f(r));
|
|
825
852
|
return r;
|
|
826
853
|
} catch (cause) {
|
|
827
854
|
return observerThrowToDefect(cause, r.tag === "Err" ? r.error : r.cause);
|
|
@@ -1089,6 +1116,11 @@ function fromNullable(value, onAbsent) {
|
|
|
1089
1116
|
* — and a thenable slipped past the types at runtime becomes a `Defect` (never
|
|
1090
1117
|
* an `Err(Promise)`), its orphaned rejection silenced.
|
|
1091
1118
|
*
|
|
1119
|
+
* `fn` is **synchronous** too. An `async` `fn` rejects *after* this boundary has
|
|
1120
|
+
* already returned, so its rejection could never reach `qualify`: it becomes a
|
|
1121
|
+
* `Defect` (never `Ok(<Promise>)`) and the orphaned rejection is silenced rather
|
|
1122
|
+
* than left to float. Reach for {@link fromPromise} to wrap async work.
|
|
1123
|
+
*
|
|
1092
1124
|
* The modeled error type is `Exclude<R, Defect>` — the `Defect` arm of
|
|
1093
1125
|
* `qualify`'s return is **subtracted** from `E`, never inferred into it. So a
|
|
1094
1126
|
* `qualify` that returns *only* `defect(cause)` yields `E = never` (a Defect is
|
|
@@ -1125,7 +1157,8 @@ function fromThrowable(fn, qualify) {
|
|
|
1125
1157
|
const triage = qualify;
|
|
1126
1158
|
return (...args) => {
|
|
1127
1159
|
try {
|
|
1128
|
-
|
|
1160
|
+
const value = fn(...args);
|
|
1161
|
+
return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
|
|
1129
1162
|
} catch (cause) {
|
|
1130
1163
|
return qualifyToResult(cause, triage);
|
|
1131
1164
|
}
|
|
@@ -1142,6 +1175,10 @@ function fromThrowable(fn, qualify) {
|
|
|
1142
1175
|
* `qualify`. When some throws *are* anticipated, reach for
|
|
1143
1176
|
* {@link fromThrowable} and triage them.
|
|
1144
1177
|
*
|
|
1178
|
+
* `fn` is **synchronous**: an `async` `fn` becomes a `Defect` (never
|
|
1179
|
+
* `Ok(<Promise>)`), with its orphaned rejection silenced rather than left to
|
|
1180
|
+
* float. Reach for {@link fromSafePromise} to wrap async work.
|
|
1181
|
+
*
|
|
1145
1182
|
* @typeParam A - the wrapped function's argument tuple.
|
|
1146
1183
|
* @typeParam T - the wrapped function's return type.
|
|
1147
1184
|
* @param fn - the throwing function to wrap.
|
|
@@ -1163,7 +1200,8 @@ function fromThrowable(fn, qualify) {
|
|
|
1163
1200
|
function fromSafeThrowable(fn) {
|
|
1164
1201
|
return (...args) => {
|
|
1165
1202
|
try {
|
|
1166
|
-
|
|
1203
|
+
const value = fn(...args);
|
|
1204
|
+
return isThenable(value) ? thenableReturnDefect(value) : Ok(value);
|
|
1167
1205
|
} catch (cause) {
|
|
1168
1206
|
return defectRes(cause);
|
|
1169
1207
|
}
|
|
@@ -1259,7 +1297,33 @@ function qualifyToResult(cause, qualify) {
|
|
|
1259
1297
|
}
|
|
1260
1298
|
}
|
|
1261
1299
|
/**
|
|
1262
|
-
*
|
|
1300
|
+
* The Defect minted when a **synchronous** boundary's `fn` returns a thenable —
|
|
1301
|
+
* i.e. an `async` function was handed to {@link fromThrowable} /
|
|
1302
|
+
* {@link fromSafeThrowable}.
|
|
1303
|
+
*
|
|
1304
|
+
* @remarks
|
|
1305
|
+
* This is the sibling of the thenable-`qualify` net in {@link qualifyToResult},
|
|
1306
|
+
* and it closes a strictly worse hole. A synchronous boundary only ever sees a
|
|
1307
|
+
* synchronous `throw`, so an async `fn`'s rejection never reaches `qualify` at
|
|
1308
|
+
* all: it would sit inside `Ok(<Promise>)`, un-triaged, and then float as an
|
|
1309
|
+
* unhandled rejection — which terminates the process on Node by default.
|
|
1310
|
+
*
|
|
1311
|
+
* Unlike the combinator callbacks, this cannot be banned at compile time
|
|
1312
|
+
* without collateral damage: `T & NotThenable<T>` on `fn`'s return makes a
|
|
1313
|
+
* **generic** function unassignable, so `fromSafeThrowable(structuredClone)`
|
|
1314
|
+
* stops compiling and `T` collapses to `unknown`. (The phantom rest-tuple guard
|
|
1315
|
+
* `fromPromise` uses fares worse.) So the ban is enforced here, at runtime,
|
|
1316
|
+
* where it costs nothing: a Defect, plus adopt-and-silence so the orphaned
|
|
1317
|
+
* rejection cannot float.
|
|
1318
|
+
*
|
|
1319
|
+
* @internal
|
|
1320
|
+
*/
|
|
1321
|
+
function thenableReturnDefect(value) {
|
|
1322
|
+
Promise.resolve(value).then(void 0, () => void 0);
|
|
1323
|
+
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."));
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Runtime thenable probe for the belt-and-braces guards above. Called inside the
|
|
1263
1327
|
* caller's `try`, so even a hostile `.then` getter lands on the Defect path.
|
|
1264
1328
|
*
|
|
1265
1329
|
* @internal
|