unthrown 5.11.0 → 5.12.1
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 +12 -3
- package/dist/index.d.cts +36 -5
- package/dist/index.d.mts +36 -5
- package/dist/index.mjs +12 -3
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1206,12 +1206,15 @@ function DoAsync() {
|
|
|
1206
1206
|
*
|
|
1207
1207
|
* @remarks
|
|
1208
1208
|
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
1209
|
-
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
1209
|
+
* falsy ones like `0`, `""`, `false`) maps to `Ok`. A throw inside `onAbsent`
|
|
1210
|
+
* becomes a `Defect`. Its return must be synchronous ({@link NotThenable});
|
|
1211
|
+
* a thenable slipped past the types becomes a `Defect`, with an orphaned
|
|
1212
|
+
* Promise silenced and a lazy thenable left unstarted.
|
|
1210
1213
|
*
|
|
1211
1214
|
* @typeParam T - the (nullable) value type.
|
|
1212
1215
|
* @typeParam E - the error produced when the value is absent.
|
|
1213
1216
|
* @param value - the possibly-absent value.
|
|
1214
|
-
* @param onAbsent - lazily produces the error for the absent case.
|
|
1217
|
+
* @param onAbsent - synchronously and lazily produces the error for the absent case.
|
|
1215
1218
|
*
|
|
1216
1219
|
* @category Interop
|
|
1217
1220
|
*
|
|
@@ -1226,7 +1229,13 @@ function DoAsync() {
|
|
|
1226
1229
|
* ```
|
|
1227
1230
|
*/
|
|
1228
1231
|
function fromNullable(value, onAbsent) {
|
|
1229
|
-
|
|
1232
|
+
if (value !== null && value !== void 0) return Ok(value);
|
|
1233
|
+
try {
|
|
1234
|
+
const error = onAbsent();
|
|
1235
|
+
return isThenable(error) ? thenableReturnDefect(error, "unthrown: fromNullable's onAbsent must be synchronous — it returned a thenable") : Err(error);
|
|
1236
|
+
} catch (cause) {
|
|
1237
|
+
return defectRes(cause);
|
|
1238
|
+
}
|
|
1230
1239
|
}
|
|
1231
1240
|
/**
|
|
1232
1241
|
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
package/dist/index.d.cts
CHANGED
|
@@ -63,6 +63,32 @@ type UniversalPattern = PatternMatcher<unknown> & {
|
|
|
63
63
|
* @internal
|
|
64
64
|
*/
|
|
65
65
|
type MatchedOf<Pt> = Pt extends PatternMatcher<infer M> ? M : Pt extends object ? { [K in keyof Pt]: MatchedOf<Pt[K]>; } : Pt;
|
|
66
|
+
/** Whether a value's type describes multiple alternatives, not one literal. */
|
|
67
|
+
type IsUnion<T, Whole = T> = T extends Whole ? ([Whole] extends [T] ? false : true) : never;
|
|
68
|
+
/**
|
|
69
|
+
* What a pattern is guaranteed to cover, rather than merely able to match.
|
|
70
|
+
*
|
|
71
|
+
* A value pattern is an equality test, so it discharges a case only when its
|
|
72
|
+
* type is a **unit type** — one inhabitant: a string / number / bigint /
|
|
73
|
+
* boolean literal, `null`, `undefined`, a `unique symbol` — or a plain object
|
|
74
|
+
* of such fields, recursively (so a variable inferred as `{ _tag: string }`
|
|
75
|
+
* cannot make an entire tagged union exhaustive). Everything else covers
|
|
76
|
+
* nothing: a widened `string`, a template literal (`` `E_${string}` `` is as
|
|
77
|
+
* wide as `string`), a union (one runtime value however many alternatives its
|
|
78
|
+
* type lists — a union of `P.*` patterns included, which is why the union test
|
|
79
|
+
* runs before the predicate one), an array or a function (compared by identity
|
|
80
|
+
* at runtime). Keep `MatchedOf` for handler narrowing; subtract only this
|
|
81
|
+
* coverage. Branded predicate patterns still cover their declared type: unlike
|
|
82
|
+
* a value, they test the whole type. An allow-list rather than a deny-list of
|
|
83
|
+
* wide types, so a shape it did not anticipate fails closed. One gap is
|
|
84
|
+
* accepted: a class instance is structurally an object type, so it recurses
|
|
85
|
+
* like a plain object while the runtime compares it by identity — pinned in
|
|
86
|
+
* `types.test-d.ts`.
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
type CoveredBy<Pt> = true extends IsUnion<Pt> ? never : [Pt] extends [PatternMatcher<infer M>] ? M : Pt extends string ? {} extends Record<Pt, never> ? never : Pt : Pt extends number ? number extends Pt ? never : Pt : Pt extends bigint ? bigint extends Pt ? never : Pt : Pt extends symbol ? symbol extends Pt ? never : Pt : Pt extends boolean | null | undefined ? Pt : Pt extends readonly unknown[] | ((...args: never) => unknown) ? never : Pt extends object ? { [K in keyof Pt]: CoveredBy<Pt[K]>; } : never;
|
|
91
|
+
type CoveredByPatterns<Pts extends readonly unknown[]> = { [I in keyof Pts]: CoveredBy<Pts[I]>; }[number];
|
|
66
92
|
/**
|
|
67
93
|
* Reject a **keyless object pattern** (`{}`) where it is written.
|
|
68
94
|
*
|
|
@@ -173,9 +199,11 @@ type Matcher<E, Remaining, O, Declared = Unset> = {
|
|
|
173
199
|
* patterns — `matcher.with(P.tag("A"), P.tag("B"), handler)`). The handler
|
|
174
200
|
* receives the input narrowed to what the patterns match (computed against
|
|
175
201
|
* `Remaining`, so cases already handled by earlier arms are excluded); the
|
|
176
|
-
*
|
|
202
|
+
* guaranteed covered cases are subtracted from `Remaining`. A widened value
|
|
203
|
+
* pattern (e.g. `{ _tag: string }`) can match but proves no coverage: use
|
|
204
|
+
* literal patterns / `P.tag` to discharge the remaining cases.
|
|
177
205
|
*/
|
|
178
|
-
with<const Pts extends readonly [unknown, ...unknown[]], O2>(...args: [...patterns: { [I in keyof Pts]: NoEmptyPattern<Pts[I]>; }, handler: (value: Extract<Remaining, MatchedOf<Pts[number]>>) => BranchReturn<Declared, O2>]): Matcher<E, Exclude<Remaining,
|
|
206
|
+
with<const Pts extends readonly [unknown, ...unknown[]], O2>(...args: [...patterns: { [I in keyof Pts]: NoEmptyPattern<Pts[I]>; }, handler: (value: Extract<Remaining, MatchedOf<Pts[number]>>) => BranchReturn<Declared, O2>]): Matcher<E, Exclude<Remaining, CoveredByPatterns<Pts>>, O | O2, Declared>;
|
|
179
207
|
/**
|
|
180
208
|
* Declare the match's output type up front: every subsequent branch handler
|
|
181
209
|
* is checked against `R`, and the match evaluates to `R` instead of the
|
|
@@ -504,12 +532,15 @@ export declare function DoAsync(): AsyncResult<{}, never>;
|
|
|
504
532
|
*
|
|
505
533
|
* @remarks
|
|
506
534
|
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
507
|
-
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
535
|
+
* falsy ones like `0`, `""`, `false`) maps to `Ok`. A throw inside `onAbsent`
|
|
536
|
+
* becomes a `Defect`. Its return must be synchronous ({@link NotThenable});
|
|
537
|
+
* a thenable slipped past the types becomes a `Defect`, with an orphaned
|
|
538
|
+
* Promise silenced and a lazy thenable left unstarted.
|
|
508
539
|
*
|
|
509
540
|
* @typeParam T - the (nullable) value type.
|
|
510
541
|
* @typeParam E - the error produced when the value is absent.
|
|
511
542
|
* @param value - the possibly-absent value.
|
|
512
|
-
* @param onAbsent - lazily produces the error for the absent case.
|
|
543
|
+
* @param onAbsent - synchronously and lazily produces the error for the absent case.
|
|
513
544
|
*
|
|
514
545
|
* @category Interop
|
|
515
546
|
*
|
|
@@ -523,7 +554,7 @@ export declare function DoAsync(): AsyncResult<{}, never>;
|
|
|
523
554
|
* fromNullable(0, () => "absent").getOr(-1); // => 0 (falsy but present)
|
|
524
555
|
* ```
|
|
525
556
|
*/
|
|
526
|
-
export declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () => E): Result<NonNullable<T>, E>;
|
|
557
|
+
export declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () => E & NotThenable<E>): Result<NonNullable<T>, E>;
|
|
527
558
|
/**
|
|
528
559
|
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
|
529
560
|
* throwing.
|
package/dist/index.d.mts
CHANGED
|
@@ -63,6 +63,32 @@ type UniversalPattern = PatternMatcher<unknown> & {
|
|
|
63
63
|
* @internal
|
|
64
64
|
*/
|
|
65
65
|
type MatchedOf<Pt> = Pt extends PatternMatcher<infer M> ? M : Pt extends object ? { [K in keyof Pt]: MatchedOf<Pt[K]>; } : Pt;
|
|
66
|
+
/** Whether a value's type describes multiple alternatives, not one literal. */
|
|
67
|
+
type IsUnion<T, Whole = T> = T extends Whole ? ([Whole] extends [T] ? false : true) : never;
|
|
68
|
+
/**
|
|
69
|
+
* What a pattern is guaranteed to cover, rather than merely able to match.
|
|
70
|
+
*
|
|
71
|
+
* A value pattern is an equality test, so it discharges a case only when its
|
|
72
|
+
* type is a **unit type** — one inhabitant: a string / number / bigint /
|
|
73
|
+
* boolean literal, `null`, `undefined`, a `unique symbol` — or a plain object
|
|
74
|
+
* of such fields, recursively (so a variable inferred as `{ _tag: string }`
|
|
75
|
+
* cannot make an entire tagged union exhaustive). Everything else covers
|
|
76
|
+
* nothing: a widened `string`, a template literal (`` `E_${string}` `` is as
|
|
77
|
+
* wide as `string`), a union (one runtime value however many alternatives its
|
|
78
|
+
* type lists — a union of `P.*` patterns included, which is why the union test
|
|
79
|
+
* runs before the predicate one), an array or a function (compared by identity
|
|
80
|
+
* at runtime). Keep `MatchedOf` for handler narrowing; subtract only this
|
|
81
|
+
* coverage. Branded predicate patterns still cover their declared type: unlike
|
|
82
|
+
* a value, they test the whole type. An allow-list rather than a deny-list of
|
|
83
|
+
* wide types, so a shape it did not anticipate fails closed. One gap is
|
|
84
|
+
* accepted: a class instance is structurally an object type, so it recurses
|
|
85
|
+
* like a plain object while the runtime compares it by identity — pinned in
|
|
86
|
+
* `types.test-d.ts`.
|
|
87
|
+
*
|
|
88
|
+
* @internal
|
|
89
|
+
*/
|
|
90
|
+
type CoveredBy<Pt> = true extends IsUnion<Pt> ? never : [Pt] extends [PatternMatcher<infer M>] ? M : Pt extends string ? {} extends Record<Pt, never> ? never : Pt : Pt extends number ? number extends Pt ? never : Pt : Pt extends bigint ? bigint extends Pt ? never : Pt : Pt extends symbol ? symbol extends Pt ? never : Pt : Pt extends boolean | null | undefined ? Pt : Pt extends readonly unknown[] | ((...args: never) => unknown) ? never : Pt extends object ? { [K in keyof Pt]: CoveredBy<Pt[K]>; } : never;
|
|
91
|
+
type CoveredByPatterns<Pts extends readonly unknown[]> = { [I in keyof Pts]: CoveredBy<Pts[I]>; }[number];
|
|
66
92
|
/**
|
|
67
93
|
* Reject a **keyless object pattern** (`{}`) where it is written.
|
|
68
94
|
*
|
|
@@ -173,9 +199,11 @@ type Matcher<E, Remaining, O, Declared = Unset> = {
|
|
|
173
199
|
* patterns — `matcher.with(P.tag("A"), P.tag("B"), handler)`). The handler
|
|
174
200
|
* receives the input narrowed to what the patterns match (computed against
|
|
175
201
|
* `Remaining`, so cases already handled by earlier arms are excluded); the
|
|
176
|
-
*
|
|
202
|
+
* guaranteed covered cases are subtracted from `Remaining`. A widened value
|
|
203
|
+
* pattern (e.g. `{ _tag: string }`) can match but proves no coverage: use
|
|
204
|
+
* literal patterns / `P.tag` to discharge the remaining cases.
|
|
177
205
|
*/
|
|
178
|
-
with<const Pts extends readonly [unknown, ...unknown[]], O2>(...args: [...patterns: { [I in keyof Pts]: NoEmptyPattern<Pts[I]>; }, handler: (value: Extract<Remaining, MatchedOf<Pts[number]>>) => BranchReturn<Declared, O2>]): Matcher<E, Exclude<Remaining,
|
|
206
|
+
with<const Pts extends readonly [unknown, ...unknown[]], O2>(...args: [...patterns: { [I in keyof Pts]: NoEmptyPattern<Pts[I]>; }, handler: (value: Extract<Remaining, MatchedOf<Pts[number]>>) => BranchReturn<Declared, O2>]): Matcher<E, Exclude<Remaining, CoveredByPatterns<Pts>>, O | O2, Declared>;
|
|
179
207
|
/**
|
|
180
208
|
* Declare the match's output type up front: every subsequent branch handler
|
|
181
209
|
* is checked against `R`, and the match evaluates to `R` instead of the
|
|
@@ -504,12 +532,15 @@ export declare function DoAsync(): AsyncResult<{}, never>;
|
|
|
504
532
|
*
|
|
505
533
|
* @remarks
|
|
506
534
|
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
507
|
-
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
535
|
+
* falsy ones like `0`, `""`, `false`) maps to `Ok`. A throw inside `onAbsent`
|
|
536
|
+
* becomes a `Defect`. Its return must be synchronous ({@link NotThenable});
|
|
537
|
+
* a thenable slipped past the types becomes a `Defect`, with an orphaned
|
|
538
|
+
* Promise silenced and a lazy thenable left unstarted.
|
|
508
539
|
*
|
|
509
540
|
* @typeParam T - the (nullable) value type.
|
|
510
541
|
* @typeParam E - the error produced when the value is absent.
|
|
511
542
|
* @param value - the possibly-absent value.
|
|
512
|
-
* @param onAbsent - lazily produces the error for the absent case.
|
|
543
|
+
* @param onAbsent - synchronously and lazily produces the error for the absent case.
|
|
513
544
|
*
|
|
514
545
|
* @category Interop
|
|
515
546
|
*
|
|
@@ -523,7 +554,7 @@ export declare function DoAsync(): AsyncResult<{}, never>;
|
|
|
523
554
|
* fromNullable(0, () => "absent").getOr(-1); // => 0 (falsy but present)
|
|
524
555
|
* ```
|
|
525
556
|
*/
|
|
526
|
-
export declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () => E): Result<NonNullable<T>, E>;
|
|
557
|
+
export declare function fromNullable<T, E>(value: T | null | undefined, onAbsent: () => E & NotThenable<E>): Result<NonNullable<T>, E>;
|
|
527
558
|
/**
|
|
528
559
|
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
|
529
560
|
* throwing.
|
package/dist/index.mjs
CHANGED
|
@@ -1205,12 +1205,15 @@ function DoAsync() {
|
|
|
1205
1205
|
*
|
|
1206
1206
|
* @remarks
|
|
1207
1207
|
* `null` and `undefined` map to `Err(onAbsent())`; any other value (including
|
|
1208
|
-
* falsy ones like `0`, `""`, `false`) maps to `Ok`.
|
|
1208
|
+
* falsy ones like `0`, `""`, `false`) maps to `Ok`. A throw inside `onAbsent`
|
|
1209
|
+
* becomes a `Defect`. Its return must be synchronous ({@link NotThenable});
|
|
1210
|
+
* a thenable slipped past the types becomes a `Defect`, with an orphaned
|
|
1211
|
+
* Promise silenced and a lazy thenable left unstarted.
|
|
1209
1212
|
*
|
|
1210
1213
|
* @typeParam T - the (nullable) value type.
|
|
1211
1214
|
* @typeParam E - the error produced when the value is absent.
|
|
1212
1215
|
* @param value - the possibly-absent value.
|
|
1213
|
-
* @param onAbsent - lazily produces the error for the absent case.
|
|
1216
|
+
* @param onAbsent - synchronously and lazily produces the error for the absent case.
|
|
1214
1217
|
*
|
|
1215
1218
|
* @category Interop
|
|
1216
1219
|
*
|
|
@@ -1225,7 +1228,13 @@ function DoAsync() {
|
|
|
1225
1228
|
* ```
|
|
1226
1229
|
*/
|
|
1227
1230
|
function fromNullable(value, onAbsent) {
|
|
1228
|
-
|
|
1231
|
+
if (value !== null && value !== void 0) return Ok(value);
|
|
1232
|
+
try {
|
|
1233
|
+
const error = onAbsent();
|
|
1234
|
+
return isThenable(error) ? thenableReturnDefect(error, "unthrown: fromNullable's onAbsent must be synchronous — it returned a thenable") : Err(error);
|
|
1235
|
+
} catch (cause) {
|
|
1236
|
+
return defectRes(cause);
|
|
1237
|
+
}
|
|
1229
1238
|
}
|
|
1230
1239
|
/**
|
|
1231
1240
|
* Wrap a throwing synchronous function so it returns a {@link Result} instead of
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unthrown",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.12.1",
|
|
4
4
|
"description": "Explicit errors as values, with a separate defect (panic) channel",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"defect",
|
|
@@ -47,11 +47,11 @@
|
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
49
49
|
"@btravstack/tsconfig": "0.4.0",
|
|
50
|
-
"@types/node": "26.6.
|
|
51
|
-
"@vitest/coverage-v8": "5.0.
|
|
50
|
+
"@types/node": "26.6.3",
|
|
51
|
+
"@vitest/coverage-v8": "5.0.2",
|
|
52
52
|
"tsdown": "0.23.0",
|
|
53
53
|
"typescript": "7.0.2",
|
|
54
|
-
"vitest": "5.0.
|
|
54
|
+
"vitest": "5.0.2"
|
|
55
55
|
},
|
|
56
56
|
"engines": {
|
|
57
57
|
"node": ">=20"
|