unthrown 5.11.0 → 5.12.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.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
- * matched cases are subtracted from `Remaining`.
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, MatchedOf<Pts[number]>>, O | O2, Declared>;
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
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
- * matched cases are subtracted from `Remaining`.
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, MatchedOf<Pts[number]>>, O | O2, Declared>;
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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "unthrown",
3
- "version": "5.11.0",
3
+ "version": "5.12.0",
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.2",
51
- "@vitest/coverage-v8": "5.0.1",
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.1"
54
+ "vitest": "5.0.2"
55
55
  },
56
56
  "engines": {
57
57
  "node": ">=20"