@onrails/pattern 0.3.3 → 0.4.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.js CHANGED
@@ -6,24 +6,34 @@ var assertNever = (value, message = "Unreachable") => {
6
6
  // src/match.ts
7
7
  var isGuard = (pattern) => typeof pattern === "function";
8
8
  var isPatternObject = (pattern) => typeof pattern === "object" && pattern !== null;
9
- var matches = (input, pattern) => {
10
- if (isGuard(pattern)) {
11
- return pattern(input);
12
- }
9
+ var compileTest = (pattern) => {
10
+ if (isGuard(pattern)) return pattern;
13
11
  if (!isPatternObject(pattern)) {
14
- return input === pattern;
15
- }
16
- if (typeof input !== "object" || input === null) {
17
- return false;
12
+ return (input) => input === pattern;
18
13
  }
19
- const record = input;
20
14
  const patternRecord = pattern;
21
- for (const key of Object.keys(patternRecord)) {
22
- if (record[key] !== patternRecord[key]) {
23
- return false;
24
- }
15
+ const keys = Object.keys(patternRecord);
16
+ if (keys.length === 0) {
17
+ return (input) => typeof input === "object" && input !== null;
18
+ }
19
+ if (keys.length === 1) {
20
+ const key = keys[0];
21
+ const expected = patternRecord[key];
22
+ return (input) => {
23
+ if (typeof input !== "object" || input === null) return false;
24
+ return input[key] === expected;
25
+ };
25
26
  }
26
- return true;
27
+ const pairs = keys.map((k) => [k, patternRecord[k]]);
28
+ return (input) => {
29
+ if (typeof input !== "object" || input === null) return false;
30
+ const record = input;
31
+ for (let i = 0; i < pairs.length; i++) {
32
+ const pair = pairs[i];
33
+ if (record[pair[0]] !== pair[1]) return false;
34
+ }
35
+ return true;
36
+ };
27
37
  };
28
38
  var NO_INPUT = /* @__PURE__ */ Symbol("@onrails/pattern/no-input");
29
39
  var findCase = (input, cases) => {
@@ -33,11 +43,19 @@ var findCase = (input, cases) => {
33
43
  }
34
44
  return null;
35
45
  };
36
- var caseForPatterns = (patterns, handler) => ({
37
- test: (input) => patterns.some((pattern) => matches(input, pattern)),
38
- run: handler
39
- });
40
- var MatchBuilder = class _MatchBuilder {
46
+ var caseForPatterns = (patterns, handler) => {
47
+ const tests = patterns.map(compileTest);
48
+ return {
49
+ test: (input) => {
50
+ for (let i = 0; i < tests.length; i++) {
51
+ if (tests[i](input)) return true;
52
+ }
53
+ return false;
54
+ },
55
+ run: handler
56
+ };
57
+ };
58
+ var MatchBuilder = class {
41
59
  /**
42
60
  * Construct a builder directly. Prefer the {@link match} factory — the
43
61
  * constructor exists so internal combinators can thread accumulated `cases`,
@@ -53,12 +71,8 @@ var MatchBuilder = class _MatchBuilder {
53
71
  _handled;
54
72
  /** Appends a runtime case plus a phantom `H` entry on the handled tuple. */
55
73
  addCase(next) {
56
- return new _MatchBuilder(
57
- [...this.cases, next],
58
- this.input,
59
- // Safe: _handled is a phantom tuple — only its type-level contents matter.
60
- this._handled
61
- );
74
+ this.cases.push(next);
75
+ return this;
62
76
  }
63
77
  /**
64
78
  * Add a case: when `input` satisfies `pattern`, run `handler` on the narrowed
@@ -87,7 +101,8 @@ var MatchBuilder = class _MatchBuilder {
87
101
  */
88
102
  with(pattern, handler) {
89
103
  return this.addCase({
90
- test: (input) => matches(input, pattern),
104
+ // Safe: P extends Pattern<T>; compiled predicate accepts the wider input T.
105
+ test: compileTest(pattern),
91
106
  run: handler
92
107
  });
93
108
  }
@@ -159,7 +174,7 @@ var MatchBuilder = class _MatchBuilder {
159
174
  * ```
160
175
  */
161
176
  returnType() {
162
- return new _MatchBuilder(this.cases, this.input, this._handled);
177
+ return this;
163
178
  }
164
179
  /**
165
180
  * Run the accumulated cases now, without a compile-time exhaustiveness check.
@@ -255,8 +270,7 @@ function match(...input) {
255
270
 
256
271
  // src/tag.ts
257
272
  var matchTag = (value, handlers) => {
258
- const tag = value._tag;
259
- const handler = handlers[tag];
273
+ const handler = handlers[value._tag];
260
274
  return handler(value);
261
275
  };
262
276
 
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/assert.ts","../src/match.ts","../src/tag.ts","../src/when.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Use in `default` branches (or after manual narrowing) to assert a union has\n * been fully handled. If every case is handled, `value` is `never` and the call\n * type-checks; an unhandled case makes `value` non-`never`, surfacing a compile\n * error. Always throws at runtime.\n *\n * @param value - the value that should be unreachable (typed `never`)\n * @param message - error message prefix; defaults to `\"Unreachable\"`\n * @throws always — the branch is meant to be unreachable\n *\n * @example\n * ```ts\n * type Shape = { kind: \"circle\"; r: number } | { kind: \"square\"; s: number };\n *\n * function area(shape: Shape): number {\n * switch (shape.kind) {\n * case \"circle\": return Math.PI * shape.r ** 2;\n * case \"square\": return shape.s ** 2;\n * default: return assertNever(shape); // type error if a case is added\n * }\n * }\n * ```\n */\nexport const assertNever = (value: never, message = \"Unreachable\"): never => {\n throw new Error(`${message}: ${String(value)}`);\n};\n","import type { ExhaustiveResult, ExhaustMatched } from \"./exhaustive.js\";\nimport type { Narrow, NarrowUnion } from \"./narrow.js\";\n\n/**\n * A pattern that matches a value of type `T`. One of:\n *\n * - **Literal** — a primitive (`\"a\"`, `42`, `true`) matched by `===`.\n * - **Object pattern** — a `Partial<T>` of shallow key/value pairs that\n * must all match by strict equality. Only available when `T` is an\n * object type.\n * - **Guard** — a function `(input: T) => boolean`. Type predicates\n * (`(x): x is U`) narrow the handler input via {@link Narrow}.\n *\n * @example\n * ```ts\n * type Event = { kind: \"click\"; x: number; y: number } | { kind: \"key\"; code: string };\n *\n * const p1: Pattern<Event> = { kind: \"click\" }; // object pattern\n * const p2: Pattern<Event> = (e) => e.kind === \"key\"; // guard\n * ```\n */\nexport type Pattern<T> = T | ObjectPattern<T> | PatternGuard<T>;\n\n// Shallow partial of `T`; restricted to object types so primitive `T`\n// (e.g. `number | string`) does not pick up `Record<string, unknown>`\n// as a candidate pattern and pollute `Narrow<T, P>` via distribution.\ntype ObjectPattern<T> = T extends object ? Partial<T> : never;\n\ntype PatternGuard<T> = (input: T) => boolean;\n\ntype Case<T, R> = {\n readonly test: (input: T) => boolean;\n readonly run: (input: T) => R;\n};\n\nconst isGuard = <T>(pattern: Pattern<T>): pattern is PatternGuard<T> =>\n typeof pattern === \"function\";\n\nconst isPatternObject = (pattern: unknown): pattern is Record<string, unknown> =>\n typeof pattern === \"object\" && pattern !== null;\n\nconst matches = <T>(input: T, pattern: Pattern<T>): boolean => {\n if (isGuard(pattern)) {\n return pattern(input);\n }\n if (!isPatternObject(pattern)) {\n return input === pattern;\n }\n if (typeof input !== \"object\" || input === null) {\n return false;\n }\n const record = input as Record<string, unknown>;\n const patternRecord = pattern as Record<string, unknown>;\n for (const key of Object.keys(patternRecord)) {\n if (record[key] !== patternRecord[key]) {\n return false;\n }\n }\n return true;\n};\n\n// Sentinel for \"curried matcher, no captured input\". Distinct from a\n// data-first `match(undefined)` / `match(null)`, which must run immediately\n// like any other value instead of silently degrading to curried mode.\nconst NO_INPUT = Symbol(\"@onrails/pattern/no-input\");\ntype NoInput = typeof NO_INPUT;\n\n// Returns the matched case, not its result, so the caller invokes `run`\n// itself in tail position (ES2015 PTC) — needed for constant-stack\n// recursive handlers, e.g. compiled alang `switch` loops on JSC. Index\n// loop, not `for..of`: a `return` inside `for..of` runs IteratorClose\n// after the call returns, disqualifying it from tail position.\nconst findCase = <T, R>(input: T, cases: readonly Case<T, R>[]): Case<T, R> | null => {\n for (let i = 0; i < cases.length; i++) {\n const c = cases[i] as Case<T, R>;\n if (c.test(input)) return c;\n }\n return null;\n};\n\nconst caseForPatterns = <T, R>(\n patterns: readonly Pattern<T>[],\n handler: (input: T) => R,\n): Case<T, R> => ({\n test: (input) => patterns.some((pattern) => matches(input, pattern)),\n run: handler,\n});\n\n// Result-type accumulator: stays at `R` when the result type is locked\n// (after `returnType<R>()`), otherwise widens with each `.with`.\ntype NextResult<Locked extends boolean, R, R2> = Locked extends true ? R : R | R2;\n\n// Handler return-type constraint: when locked, every handler must return `R`;\n// when open, the handler can return any type and the result widens to include it.\ntype HandlerReturn<Locked extends boolean, R, R2> = Locked extends true ? R : R2;\n\n/**\n * Fluent builder for {@link match}. Tracks the handled cases at the type\n * level so `.exhaustive()` only compiles when every union member is covered.\n *\n * The `Locked` phantom flag (set via `.returnType<R>()`) constrains every\n * subsequent handler to return `R`, useful when branch return-type\n * inference would widen to a union narrower than the target slot\n * (e.g. `ReactNode`, an API DTO).\n *\n * @example\n * ```ts\n * const describe = match<Event>()\n * .with({ kind: \"click\" }, (e) => `click @ ${e.x},${e.y}`)\n * .with({ kind: \"key\" }, (e) => `key ${e.code}`)\n * .exhaustive();\n * ```\n */\nexport class MatchBuilder<\n T,\n R = never,\n HasInput extends boolean = false,\n Handled extends readonly unknown[] = [],\n Locked extends boolean = false,\n> {\n /**\n * Construct a builder directly. Prefer the {@link match} factory — the\n * constructor exists so internal combinators can thread accumulated `cases`,\n * the optional captured `input`, and the phantom `_handled` tuple.\n */\n constructor(\n private readonly cases: readonly Case<T, unknown>[],\n // No default: `= NO_INPUT` would swallow a data-first `undefined` input,\n // because JS applies parameter defaults to explicitly-passed undefined.\n private readonly input: T | NoInput,\n /** Phantom tuple of handled cases — only its compile-time contents matter. */\n readonly _handled: Handled = [] as unknown as Handled,\n ) {}\n\n /** Appends a runtime case plus a phantom `H` entry on the handled tuple. */\n private addCase<R2, H>(\n next: Case<T, unknown>,\n ): MatchBuilder<T, NextResult<Locked, R, R2>, HasInput, readonly [...Handled, H], Locked> {\n return new MatchBuilder(\n [...this.cases, next],\n this.input,\n // Safe: _handled is a phantom tuple — only its type-level contents matter.\n this._handled as unknown as readonly [...Handled, H],\n );\n }\n\n /**\n * Add a case: when `input` satisfies `pattern`, run `handler` on the narrowed\n * value. The pattern may be a literal, a shallow object pattern, or a guard\n * ({@link Pattern}); a type-predicate guard narrows the handler input. Each\n * `.with` advances compile-time exhaustiveness tracking so `.exhaustive()`\n * knows which union members remain.\n *\n * @param pattern - literal, object pattern, or guard to test against\n * @param handler - run on the narrowed input when the pattern matches\n * @returns a builder with this case appended\n *\n * @example\n * ```ts\n * type Event =\n * | { type: \"message\"; content: string }\n * | { type: \"error\"; message: string }\n * | { type: \"done\" };\n *\n * const summary = match({ type: \"message\", content: \"hi\" } as Event)\n * .with({ type: \"message\" }, (e) => e.content) // e: { type: \"message\"; content: string }\n * .with({ type: \"error\" }, (e) => e.message)\n * .with({ type: \"done\" }, () => \"—\")\n * .exhaustive(); // \"hi\"\n * ```\n */\n with<const P extends Pattern<T>, R2>(\n pattern: P,\n handler: (input: Narrow<T, P>) => HandlerReturn<Locked, R, R2>,\n ): MatchBuilder<\n T,\n NextResult<Locked, R, R2>,\n HasInput,\n readonly [...Handled, ExhaustMatched<T, P>],\n Locked\n > {\n return this.addCase<R2, ExhaustMatched<T, P>>({\n test: (input) => matches(input, pattern),\n run: handler as (input: T) => unknown,\n });\n }\n\n /**\n * One handler for several patterns (OR). The handler input is the union of\n * the members narrowed by each pattern, and all of them are marked handled\n * for exhaustiveness.\n *\n * @param patterns - patterns to test; any match runs `handler`\n * @param handler - run on the union of narrowed members\n * @returns a builder with the shared case appended\n *\n * @example\n * ```ts\n * type Job =\n * | { kind: \"queued\"; id: string }\n * | { kind: \"running\"; id: string }\n * | { kind: \"done\"; ok: boolean };\n *\n * const status = match({ kind: \"running\", id: \"j1\" } as Job)\n * .withOneOf([{ kind: \"queued\" }, { kind: \"running\" }], (j) => j.id) // j: queued | running\n * .with({ kind: \"done\" }, (j) => (j.ok ? \"yes\" : \"no\"))\n * .exhaustive(); // \"j1\"\n * ```\n */\n withOneOf<const Ps extends readonly Pattern<T>[], R2>(\n patterns: Ps,\n handler: (input: NarrowUnion<T, Ps>) => HandlerReturn<Locked, R, R2>,\n ): MatchBuilder<\n T,\n NextResult<Locked, R, R2>,\n HasInput,\n readonly [...Handled, NarrowUnion<T, Ps>],\n Locked\n > {\n return this.addCase<R2, NarrowUnion<T, Ps>>(\n caseForPatterns(patterns, handler as (input: T) => unknown),\n );\n }\n\n /**\n * {@link withOneOf} for exactly two patterns — sugar that avoids the array.\n *\n * @param pattern1 - first pattern\n * @param pattern2 - second pattern\n * @param handler - run on the union of the two narrowed members\n * @returns a builder with the shared case appended\n *\n * @example\n * ```ts\n * type Provider = \"ollama\" | \"openrouter\";\n *\n * const pick = match<Provider>()\n * .withEither(\"ollama\", \"openrouter\", (p) => p) // p: \"ollama\" | \"openrouter\"\n * .exhaustive();\n * pick(\"ollama\"); // \"ollama\"\n * ```\n */\n withEither<const P1 extends Pattern<T>, const P2 extends Pattern<T>, R2>(\n pattern1: P1,\n pattern2: P2,\n handler: (input: NarrowUnion<T, readonly [P1, P2]>) => HandlerReturn<Locked, R, R2>,\n ): MatchBuilder<\n T,\n NextResult<Locked, R, R2>,\n HasInput,\n readonly [...Handled, NarrowUnion<T, readonly [P1, P2]>],\n Locked\n > {\n return this.withOneOf([pattern1, pattern2], handler);\n }\n\n /**\n * Lock the result type. All subsequent `.with()` handlers must return `R2`.\n * Useful when branch return-type inference widens to a union narrower than\n * the slot the match feeds into (e.g. `ReactNode`).\n *\n * Also the fix for literal-widening: when a handler returns an object whose\n * field is a union literal, inference widens it (e.g. `{ mode: \"native\" }`\n * becomes `{ mode: string }`, breaking a `Mode` union). Lock the target type\n * instead of sprinkling `as const`:\n *\n * @example\n * ```ts\n * type State = { mode: \"compat\" | \"native\" };\n * const next = match(flag)\n * .returnType<State>() // handlers now checked against State\n * .with(\"--native\", () => ({ mode: \"native\" })) // no `as const` needed\n * .otherwise(() => state);\n * ```\n */\n returnType<R2>(): MatchBuilder<T, R2, HasInput, Handled, true> {\n return new MatchBuilder<T, R2, HasInput, Handled, true>(this.cases, this.input, this._handled);\n }\n\n /**\n * Run the accumulated cases now, without a compile-time exhaustiveness check.\n * Uses the value passed to {@link match} (data-first), or the value passed to\n * `run` (curried). Throws `\"Non-exhaustive match\"` if no case matches at\n * runtime — prefer {@link exhaustive} when the input is a closed union, or\n * {@link otherwise} for an explicit fallback.\n *\n * @param input - the value to match when the builder was created curried\n * @returns the matched handler's result\n * @throws if no case matches the input\n *\n * @example\n * ```ts\n * type Event = { type: \"error\"; message: string } | { type: \"done\" };\n *\n * const msg = match({ type: \"error\", message: \"x\" } as Event)\n * .with({ type: \"error\" }, (e) => e.message)\n * .run({ type: \"error\", message: \"net\" }); // \"net\"\n * ```\n */\n run(...input: readonly [input: T] | readonly []): R {\n const value = input.length === 1 ? input[0] : this.input;\n if (value === NO_INPUT) {\n throw new Error(\"match.run: no input value\");\n }\n const c = findCase(value, this.cases);\n if (c === null) {\n throw new Error(\"Non-exhaustive match: no case matched input\");\n }\n // Tail call: recursive handlers run in constant stack (see findCase).\n return c.run(value) as R;\n }\n\n /**\n * Settle the match with a compile-time exhaustiveness guarantee. Only\n * type-checks when every member of `T` has been handled; otherwise the return\n * type becomes {@link NonExhaustiveError}, surfacing the missing cases as a\n * type error. Returns the result directly when data-first ({@link match}\n * called with a value), or a reusable `(input: T) => R` matcher when curried.\n *\n * @returns the result (data-first) or a matcher function (curried)\n *\n * @example\n * ```ts\n * type Event =\n * | { type: \"message\"; content: string }\n * | { type: \"error\"; message: string }\n * | { type: \"done\" };\n *\n * // Curried — reusable matcher; omitting a `.with` would be a type error.\n * const describe = match<Event>()\n * .with({ type: \"message\" }, (e) => e.content)\n * .with({ type: \"error\" }, (e) => `! ${e.message}`)\n * .with({ type: \"done\" }, () => \"done\")\n * .exhaustive();\n * describe({ type: \"done\" }); // \"done\"\n * ```\n */\n exhaustive(): ExhaustiveResult<T, Handled, R, HasInput> {\n // Safe: HasInput is true exactly when match(value) captured an input.\n return (\n this.input === NO_INPUT ? (value: T) => this.run(value) : this.run()\n ) as ExhaustiveResult<T, Handled, R, HasInput>;\n }\n\n /**\n * Settle the match with a catch-all fallback, dropping the exhaustiveness\n * requirement — `handler` runs (on the full input type) when no case matched.\n * Returns the result directly when data-first, or a reusable matcher when\n * curried.\n *\n * @param handler - fallback run on the input when no prior case matched\n * @returns the result (data-first) or a matcher function (curried)\n *\n * @example\n * ```ts\n * type Event =\n * | { type: \"message\"; content: string }\n * | { type: \"error\"; message: string }\n * | { type: \"done\" };\n *\n * const len = match({ type: \"done\" } as Event)\n * .with({ type: \"message\" }, (e) => e.content.length)\n * .otherwise(() => -1); // -1\n * ```\n */\n otherwise(handler: (input: T) => R): HasInput extends true ? R : (input: T) => R {\n const runWithFallback = (value: T): R => {\n const c = findCase(value, this.cases);\n // Both branches are tail calls (see findCase).\n return c === null ? handler(value) : (c.run(value) as R);\n };\n // Safe: HasInput is true exactly when match(value) captured an input.\n return (\n this.input === NO_INPUT ? runWithFallback : runWithFallback(this.input)\n ) as HasInput extends true ? R : (input: T) => R;\n }\n}\n\n/**\n * Start a pattern-matching expression. Two call shapes:\n *\n * - `match(value)` — data-first; subsequent `.exhaustive()` or\n * `.otherwise(fn)` runs immediately against `value`.\n * - `match<T>()` — curried; subsequent `.exhaustive()` returns a function\n * `(value: T) => R` for use in `pipe(...)` or as a reusable matcher.\n *\n * @example\n * ```ts\n * // Data-first\n * const out = match({ kind: \"click\", x: 1, y: 2 } as Event)\n * .with({ kind: \"click\" }, (e) => e.x + e.y)\n * .with({ kind: \"key\" }, () => -1)\n * .exhaustive();\n *\n * // Curried — build a reusable matcher\n * const describe = match<Event>()\n * .with({ kind: \"click\" }, (e) => `click @ ${e.x},${e.y}`)\n * .with({ kind: \"key\" }, (e) => `key ${e.code}`)\n * .exhaustive();\n * ```\n */\nexport function match<T>(input: T): MatchBuilder<T, never, true>;\nexport function match<T = never>(): MatchBuilder<T, never, false>;\nexport function match<T>(\n ...input: readonly [input: T] | readonly []\n): MatchBuilder<T, never, boolean> {\n return input.length === 1\n ? new MatchBuilder<T, never, boolean>([], input[0])\n : new MatchBuilder<T, never, boolean>([], NO_INPUT);\n}\n","type Tagged = { readonly _tag: string };\n\ntype TagHandlers<T extends Tagged, R> = {\n [K in T[\"_tag\"]]: (value: Extract<T, { _tag: K }>) => R;\n};\n\n/**\n * Exhaustive match on `_tag` — for `@onrails/result` / `@onrails/maybe` style\n * unions. The `handlers` object must supply one branch per `_tag`, and each\n * handler receives the member narrowed to its tag.\n *\n * @param value - a tagged union value to dispatch on\n * @param handlers - one handler per `_tag`, keyed by tag\n * @returns the result of the matched handler\n *\n * @example\n * ```ts\n * import { err, ok, type Result } from \"@onrails/result\";\n *\n * const label = (r: Result<number, string>) =>\n * matchTag(r, {\n * Ok: (v) => `ok:${v.value}`, // v: Ok<number>\n * Err: (e) => `err:${e.error}`, // e: Err<string>\n * });\n * label(ok(1)); // \"ok:1\"\n * label(err(\"x\")); // \"err:x\"\n * ```\n */\nexport const matchTag = <T extends Tagged, R>(value: T, handlers: TagHandlers<T, R>): R => {\n const tag = value._tag as T[\"_tag\"];\n const handler = handlers[tag] as (value: Extract<T, { _tag: typeof tag }>) => R;\n return handler(value as Extract<T, { _tag: typeof tag }>);\n};\n","import type { Pattern } from \"./match.js\";\n\n/**\n * Guard pattern for {@link match}.with.\n *\n * Accepts plain boolean guards (`(x) => x.length > 0`) or TS type-predicate\n * guards (`(x): x is Foo => ...`). For type predicates, the matched handler's\n * input is narrowed to the predicate's target type via {@link Narrow}.\n *\n * Note: a plain boolean guard does not advance exhaustiveness tracking — pair\n * it with `.otherwise()`, or use a type predicate to narrow the union.\n *\n * @param guard - boolean or type-predicate guard function\n * @returns the guard, usable as a {@link Pattern} in `.with`\n *\n * @example\n * ```ts\n * const sign = match<number>()\n * .with(when((n: number) => n < 0), () => \"neg\")\n * .with(when((n: number) => n >= 0), () => \"non-neg\")\n * .exhaustive();\n * sign(-1); // \"neg\"\n * sign(0); // \"non-neg\"\n * ```\n */\nexport function when<T, U extends T>(guard: (input: T) => input is U): (input: T) => input is U;\nexport function when<T>(guard: (input: T) => boolean): Pattern<T>;\nexport function when<T>(guard: (input: T) => boolean): Pattern<T> {\n return guard;\n}\n"]}
1
+ {"version":3,"sources":["../src/assert.ts","../src/match.ts","../src/tag.ts","../src/when.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["/**\n * Use in `default` branches (or after manual narrowing) to assert a union has\n * been fully handled. If every case is handled, `value` is `never` and the call\n * type-checks; an unhandled case makes `value` non-`never`, surfacing a compile\n * error. Always throws at runtime.\n *\n * @param value - the value that should be unreachable (typed `never`)\n * @param message - error message prefix; defaults to `\"Unreachable\"`\n * @throws always — the branch is meant to be unreachable\n *\n * @example\n * ```ts\n * type Shape = { kind: \"circle\"; r: number } | { kind: \"square\"; s: number };\n *\n * function area(shape: Shape): number {\n * switch (shape.kind) {\n * case \"circle\": return Math.PI * shape.r ** 2;\n * case \"square\": return shape.s ** 2;\n * default: return assertNever(shape); // type error if a case is added\n * }\n * }\n * ```\n */\nexport const assertNever = (value: never, message = \"Unreachable\"): never => {\n throw new Error(`${message}: ${String(value)}`);\n};\n","import type { ExhaustiveResult, ExhaustMatched } from \"./exhaustive.js\";\nimport type { Narrow, NarrowUnion } from \"./narrow.js\";\n\n/**\n * A pattern that matches a value of type `T`. One of:\n *\n * - **Literal** — a primitive (`\"a\"`, `42`, `true`) matched by `===`.\n * - **Object pattern** — a `Partial<T>` of shallow key/value pairs that\n * must all match by strict equality. Only available when `T` is an\n * object type.\n * - **Guard** — a function `(input: T) => boolean`. Type predicates\n * (`(x): x is U`) narrow the handler input via {@link Narrow}.\n *\n * @example\n * ```ts\n * type Event = { kind: \"click\"; x: number; y: number } | { kind: \"key\"; code: string };\n *\n * const p1: Pattern<Event> = { kind: \"click\" }; // object pattern\n * const p2: Pattern<Event> = (e) => e.kind === \"key\"; // guard\n * ```\n */\nexport type Pattern<T> = T | ObjectPattern<T> | PatternGuard<T>;\n\n// Shallow partial of `T`; restricted to object types so primitive `T`\n// (e.g. `number | string`) does not pick up `Record<string, unknown>`\n// as a candidate pattern and pollute `Narrow<T, P>` via distribution.\ntype ObjectPattern<T> = T extends object ? Partial<T> : never;\n\ntype PatternGuard<T> = (input: T) => boolean;\n\ntype Case<T, R> = {\n readonly test: (input: T) => boolean;\n readonly run: (input: T) => R;\n};\n\nconst isGuard = <T>(pattern: Pattern<T>): pattern is PatternGuard<T> =>\n typeof pattern === \"function\";\n\nconst isPatternObject = (pattern: unknown): pattern is Record<string, unknown> =>\n typeof pattern === \"object\" && pattern !== null;\n\n/**\n * Compile a pattern into a reusable predicate once at `.with` time.\n * Single-key object patterns (the common discriminant shape) skip\n * `Object.keys` on every test.\n */\nconst compileTest = <T>(pattern: Pattern<T>): ((input: T) => boolean) => {\n if (isGuard(pattern)) return pattern;\n if (!isPatternObject(pattern)) {\n return (input) => input === pattern;\n }\n const patternRecord = pattern as Record<string, unknown>;\n const keys = Object.keys(patternRecord);\n if (keys.length === 0) {\n return (input) => typeof input === \"object\" && input !== null;\n }\n if (keys.length === 1) {\n const key = keys[0] as string;\n const expected = patternRecord[key];\n return (input) => {\n if (typeof input !== \"object\" || input === null) return false;\n return (input as Record<string, unknown>)[key] === expected;\n };\n }\n const pairs: readonly (readonly [string, unknown])[] = keys.map((k) => [k, patternRecord[k]]);\n return (input) => {\n if (typeof input !== \"object\" || input === null) return false;\n const record = input as Record<string, unknown>;\n for (let i = 0; i < pairs.length; i++) {\n const pair = pairs[i] as readonly [string, unknown];\n if (record[pair[0]] !== pair[1]) return false;\n }\n return true;\n };\n};\n\n// Sentinel for \"curried matcher, no captured input\". Distinct from a\n// data-first `match(undefined)` / `match(null)`, which must run immediately\n// like any other value instead of silently degrading to curried mode.\nconst NO_INPUT = Symbol(\"@onrails/pattern/no-input\");\ntype NoInput = typeof NO_INPUT;\n\n// Returns the matched case, not its result, so the caller invokes `run`\n// itself in tail position (ES2015 PTC) — needed for constant-stack\n// recursive handlers, e.g. compiled alang `switch` loops on JSC. Index\n// loop, not `for..of`: a `return` inside `for..of` runs IteratorClose\n// after the call returns, disqualifying it from tail position.\nconst findCase = <T, R>(input: T, cases: readonly Case<T, R>[]): Case<T, R> | null => {\n for (let i = 0; i < cases.length; i++) {\n const c = cases[i] as Case<T, R>;\n if (c.test(input)) return c;\n }\n return null;\n};\n\nconst caseForPatterns = <T, R>(\n patterns: readonly Pattern<T>[],\n handler: (input: T) => R,\n): Case<T, R> => {\n const tests = patterns.map(compileTest);\n return {\n test: (input) => {\n for (let i = 0; i < tests.length; i++) {\n if ((tests[i] as (input: T) => boolean)(input)) return true;\n }\n return false;\n },\n run: handler,\n };\n};\n\n// Result-type accumulator: stays at `R` when the result type is locked\n// (after `returnType<R>()`), otherwise widens with each `.with`.\ntype NextResult<Locked extends boolean, R, R2> = Locked extends true ? R : R | R2;\n\n// Handler return-type constraint: when locked, every handler must return `R`;\n// when open, the handler can return any type and the result widens to include it.\ntype HandlerReturn<Locked extends boolean, R, R2> = Locked extends true ? R : R2;\n\n/**\n * Fluent builder for {@link match}. Tracks the handled cases at the type\n * level so `.exhaustive()` only compiles when every union member is covered.\n *\n * The `Locked` phantom flag (set via `.returnType<R>()`) constrains every\n * subsequent handler to return `R`, useful when branch return-type\n * inference would widen to a union narrower than the target slot\n * (e.g. `ReactNode`, an API DTO).\n *\n * @example\n * ```ts\n * const describe = match<Event>()\n * .with({ kind: \"click\" }, (e) => `click @ ${e.x},${e.y}`)\n * .with({ kind: \"key\" }, (e) => `key ${e.code}`)\n * .exhaustive();\n * ```\n */\nexport class MatchBuilder<\n T,\n R = never,\n HasInput extends boolean = false,\n Handled extends readonly unknown[] = [],\n Locked extends boolean = false,\n> {\n /**\n * Construct a builder directly. Prefer the {@link match} factory — the\n * constructor exists so internal combinators can thread accumulated `cases`,\n * the optional captured `input`, and the phantom `_handled` tuple.\n */\n constructor(\n // Mutable: linear `.with` chains push in place. Forking an intermediate\n // builder shares the case list — builders are not fork-safe by design.\n private readonly cases: Case<T, unknown>[],\n // No default: `= NO_INPUT` would swallow a data-first `undefined` input,\n // because JS applies parameter defaults to explicitly-passed undefined.\n private readonly input: T | NoInput,\n /** Phantom tuple of handled cases — only its compile-time contents matter. */\n readonly _handled: Handled = [] as unknown as Handled,\n ) {}\n\n /** Appends a runtime case plus a phantom `H` entry on the handled tuple. */\n private addCase<R2, H>(\n next: Case<T, unknown>,\n ): MatchBuilder<T, NextResult<Locked, R, R2>, HasInput, readonly [...Handled, H], Locked> {\n this.cases.push(next);\n // Safe: same instance; only phantom type params change (`_handled` is compile-time only).\n return this as unknown as MatchBuilder<\n T,\n NextResult<Locked, R, R2>,\n HasInput,\n readonly [...Handled, H],\n Locked\n >;\n }\n\n /**\n * Add a case: when `input` satisfies `pattern`, run `handler` on the narrowed\n * value. The pattern may be a literal, a shallow object pattern, or a guard\n * ({@link Pattern}); a type-predicate guard narrows the handler input. Each\n * `.with` advances compile-time exhaustiveness tracking so `.exhaustive()`\n * knows which union members remain.\n *\n * @param pattern - literal, object pattern, or guard to test against\n * @param handler - run on the narrowed input when the pattern matches\n * @returns a builder with this case appended\n *\n * @example\n * ```ts\n * type Event =\n * | { type: \"message\"; content: string }\n * | { type: \"error\"; message: string }\n * | { type: \"done\" };\n *\n * const summary = match({ type: \"message\", content: \"hi\" } as Event)\n * .with({ type: \"message\" }, (e) => e.content) // e: { type: \"message\"; content: string }\n * .with({ type: \"error\" }, (e) => e.message)\n * .with({ type: \"done\" }, () => \"—\")\n * .exhaustive(); // \"hi\"\n * ```\n */\n with<const P extends Pattern<T>, R2>(\n pattern: P,\n handler: (input: Narrow<T, P>) => HandlerReturn<Locked, R, R2>,\n ): MatchBuilder<\n T,\n NextResult<Locked, R, R2>,\n HasInput,\n readonly [...Handled, ExhaustMatched<T, P>],\n Locked\n > {\n return this.addCase<R2, ExhaustMatched<T, P>>({\n // Safe: P extends Pattern<T>; compiled predicate accepts the wider input T.\n test: compileTest(pattern) as (input: T) => boolean,\n run: handler as (input: T) => unknown,\n });\n }\n\n /**\n * One handler for several patterns (OR). The handler input is the union of\n * the members narrowed by each pattern, and all of them are marked handled\n * for exhaustiveness.\n *\n * @param patterns - patterns to test; any match runs `handler`\n * @param handler - run on the union of narrowed members\n * @returns a builder with the shared case appended\n *\n * @example\n * ```ts\n * type Job =\n * | { kind: \"queued\"; id: string }\n * | { kind: \"running\"; id: string }\n * | { kind: \"done\"; ok: boolean };\n *\n * const status = match({ kind: \"running\", id: \"j1\" } as Job)\n * .withOneOf([{ kind: \"queued\" }, { kind: \"running\" }], (j) => j.id) // j: queued | running\n * .with({ kind: \"done\" }, (j) => (j.ok ? \"yes\" : \"no\"))\n * .exhaustive(); // \"j1\"\n * ```\n */\n withOneOf<const Ps extends readonly Pattern<T>[], R2>(\n patterns: Ps,\n handler: (input: NarrowUnion<T, Ps>) => HandlerReturn<Locked, R, R2>,\n ): MatchBuilder<\n T,\n NextResult<Locked, R, R2>,\n HasInput,\n readonly [...Handled, NarrowUnion<T, Ps>],\n Locked\n > {\n return this.addCase<R2, NarrowUnion<T, Ps>>(\n caseForPatterns(patterns, handler as (input: T) => unknown),\n );\n }\n\n /**\n * {@link withOneOf} for exactly two patterns — sugar that avoids the array.\n *\n * @param pattern1 - first pattern\n * @param pattern2 - second pattern\n * @param handler - run on the union of the two narrowed members\n * @returns a builder with the shared case appended\n *\n * @example\n * ```ts\n * type Provider = \"ollama\" | \"openrouter\";\n *\n * const pick = match<Provider>()\n * .withEither(\"ollama\", \"openrouter\", (p) => p) // p: \"ollama\" | \"openrouter\"\n * .exhaustive();\n * pick(\"ollama\"); // \"ollama\"\n * ```\n */\n withEither<const P1 extends Pattern<T>, const P2 extends Pattern<T>, R2>(\n pattern1: P1,\n pattern2: P2,\n handler: (input: NarrowUnion<T, readonly [P1, P2]>) => HandlerReturn<Locked, R, R2>,\n ): MatchBuilder<\n T,\n NextResult<Locked, R, R2>,\n HasInput,\n readonly [...Handled, NarrowUnion<T, readonly [P1, P2]>],\n Locked\n > {\n return this.withOneOf([pattern1, pattern2], handler);\n }\n\n /**\n * Lock the result type. All subsequent `.with()` handlers must return `R2`.\n * Useful when branch return-type inference widens to a union narrower than\n * the slot the match feeds into (e.g. `ReactNode`).\n *\n * Also the fix for literal-widening: when a handler returns an object whose\n * field is a union literal, inference widens it (e.g. `{ mode: \"native\" }`\n * becomes `{ mode: string }`, breaking a `Mode` union). Lock the target type\n * instead of sprinkling `as const`:\n *\n * @example\n * ```ts\n * type State = { mode: \"compat\" | \"native\" };\n * const next = match(flag)\n * .returnType<State>() // handlers now checked against State\n * .with(\"--native\", () => ({ mode: \"native\" })) // no `as const` needed\n * .otherwise(() => state);\n * ```\n */\n returnType<R2>(): MatchBuilder<T, R2, HasInput, Handled, true> {\n // Safe: same instance; only the Locked phantom + result type change.\n return this as unknown as MatchBuilder<T, R2, HasInput, Handled, true>;\n }\n\n /**\n * Run the accumulated cases now, without a compile-time exhaustiveness check.\n * Uses the value passed to {@link match} (data-first), or the value passed to\n * `run` (curried). Throws `\"Non-exhaustive match\"` if no case matches at\n * runtime — prefer {@link exhaustive} when the input is a closed union, or\n * {@link otherwise} for an explicit fallback.\n *\n * @param input - the value to match when the builder was created curried\n * @returns the matched handler's result\n * @throws if no case matches the input\n *\n * @example\n * ```ts\n * type Event = { type: \"error\"; message: string } | { type: \"done\" };\n *\n * const msg = match({ type: \"error\", message: \"x\" } as Event)\n * .with({ type: \"error\" }, (e) => e.message)\n * .run({ type: \"error\", message: \"net\" }); // \"net\"\n * ```\n */\n run(...input: readonly [input: T] | readonly []): R {\n const value = input.length === 1 ? input[0] : this.input;\n if (value === NO_INPUT) {\n throw new Error(\"match.run: no input value\");\n }\n const c = findCase(value, this.cases);\n if (c === null) {\n throw new Error(\"Non-exhaustive match: no case matched input\");\n }\n // Tail call: recursive handlers run in constant stack (see findCase).\n return c.run(value) as R;\n }\n\n /**\n * Settle the match with a compile-time exhaustiveness guarantee. Only\n * type-checks when every member of `T` has been handled; otherwise the return\n * type becomes {@link NonExhaustiveError}, surfacing the missing cases as a\n * type error. Returns the result directly when data-first ({@link match}\n * called with a value), or a reusable `(input: T) => R` matcher when curried.\n *\n * @returns the result (data-first) or a matcher function (curried)\n *\n * @example\n * ```ts\n * type Event =\n * | { type: \"message\"; content: string }\n * | { type: \"error\"; message: string }\n * | { type: \"done\" };\n *\n * // Curried — reusable matcher; omitting a `.with` would be a type error.\n * const describe = match<Event>()\n * .with({ type: \"message\" }, (e) => e.content)\n * .with({ type: \"error\" }, (e) => `! ${e.message}`)\n * .with({ type: \"done\" }, () => \"done\")\n * .exhaustive();\n * describe({ type: \"done\" }); // \"done\"\n * ```\n */\n exhaustive(): ExhaustiveResult<T, Handled, R, HasInput> {\n // Safe: HasInput is true exactly when match(value) captured an input.\n return (\n this.input === NO_INPUT ? (value: T) => this.run(value) : this.run()\n ) as ExhaustiveResult<T, Handled, R, HasInput>;\n }\n\n /**\n * Settle the match with a catch-all fallback, dropping the exhaustiveness\n * requirement — `handler` runs (on the full input type) when no case matched.\n * Returns the result directly when data-first, or a reusable matcher when\n * curried.\n *\n * @param handler - fallback run on the input when no prior case matched\n * @returns the result (data-first) or a matcher function (curried)\n *\n * @example\n * ```ts\n * type Event =\n * | { type: \"message\"; content: string }\n * | { type: \"error\"; message: string }\n * | { type: \"done\" };\n *\n * const len = match({ type: \"done\" } as Event)\n * .with({ type: \"message\" }, (e) => e.content.length)\n * .otherwise(() => -1); // -1\n * ```\n */\n otherwise(handler: (input: T) => R): HasInput extends true ? R : (input: T) => R {\n const runWithFallback = (value: T): R => {\n const c = findCase(value, this.cases);\n // Both branches are tail calls (see findCase).\n return c === null ? handler(value) : (c.run(value) as R);\n };\n // Safe: HasInput is true exactly when match(value) captured an input.\n return (\n this.input === NO_INPUT ? runWithFallback : runWithFallback(this.input)\n ) as HasInput extends true ? R : (input: T) => R;\n }\n}\n\n/**\n * Start a pattern-matching expression. Two call shapes:\n *\n * - `match(value)` — data-first; subsequent `.exhaustive()` or\n * `.otherwise(fn)` runs immediately against `value`.\n * - `match<T>()` — curried; subsequent `.exhaustive()` returns a function\n * `(value: T) => R` for use in `pipe(...)` or as a reusable matcher.\n *\n * @example\n * ```ts\n * // Data-first\n * const out = match({ kind: \"click\", x: 1, y: 2 } as Event)\n * .with({ kind: \"click\" }, (e) => e.x + e.y)\n * .with({ kind: \"key\" }, () => -1)\n * .exhaustive();\n *\n * // Curried — build a reusable matcher\n * const describe = match<Event>()\n * .with({ kind: \"click\" }, (e) => `click @ ${e.x},${e.y}`)\n * .with({ kind: \"key\" }, (e) => `key ${e.code}`)\n * .exhaustive();\n * ```\n */\nexport function match<T>(input: T): MatchBuilder<T, never, true>;\nexport function match<T = never>(): MatchBuilder<T, never, false>;\nexport function match<T>(\n ...input: readonly [input: T] | readonly []\n): MatchBuilder<T, never, boolean> {\n return input.length === 1\n ? new MatchBuilder<T, never, boolean>([], input[0])\n : new MatchBuilder<T, never, boolean>([], NO_INPUT);\n}\n","type Tagged = { readonly _tag: string };\n\ntype TagHandlers<T extends Tagged> = {\n [K in T[\"_tag\"]]: (value: Extract<T, { _tag: K }>) => unknown;\n};\n\n// Return type is read off the handlers, not inferred through a mapped type: with `R` as a type\n// parameter of `TagHandlers`, TypeScript 7 infers `unknown` for the result.\ntype HandlerResult<Hs> = {\n [K in keyof Hs]: Hs[K] extends (...args: never[]) => infer U ? U : never;\n}[keyof Hs];\n\n/**\n * Exhaustive match on `_tag` — for `@onrails/result` / `@onrails/maybe` style\n * unions. The `handlers` object must supply one branch per `_tag`, and each\n * handler receives the member narrowed to its tag.\n *\n * @param value - a tagged union value to dispatch on\n * @param handlers - one handler per `_tag`, keyed by tag\n * @returns the result of the matched handler\n *\n * @example\n * ```ts\n * import { err, ok, type Result } from \"@onrails/result\";\n *\n * const label = (r: Result<number, string>) =>\n * matchTag(r, {\n * Ok: (v) => `ok:${v.value}`, // v: Ok<number>\n * Err: (e) => `err:${e.error}`, // e: Err<string>\n * });\n * label(ok(1)); // \"ok:1\"\n * label(err(\"x\")); // \"err:x\"\n * ```\n */\nexport const matchTag = <T extends Tagged, Hs extends TagHandlers<T>>(\n value: T,\n // Excess keys map to `never`, restoring the excess-property check lost by inferring `Hs`.\n handlers: Hs & { [K in Exclude<keyof Hs, T[\"_tag\"]>]: never },\n): HandlerResult<Hs> => {\n const handler = handlers[value._tag as keyof Hs] as (value: T) => HandlerResult<Hs>;\n return handler(value);\n};\n","import type { Pattern } from \"./match.js\";\n\n/**\n * Guard pattern for {@link match}.with.\n *\n * Accepts plain boolean guards (`(x) => x.length > 0`) or TS type-predicate\n * guards (`(x): x is Foo => ...`). For type predicates, the matched handler's\n * input is narrowed to the predicate's target type via {@link Narrow}.\n *\n * Note: a plain boolean guard does not advance exhaustiveness tracking — pair\n * it with `.otherwise()`, or use a type predicate to narrow the union.\n *\n * @param guard - boolean or type-predicate guard function\n * @returns the guard, usable as a {@link Pattern} in `.with`\n *\n * @example\n * ```ts\n * const sign = match<number>()\n * .with(when((n: number) => n < 0), () => \"neg\")\n * .with(when((n: number) => n >= 0), () => \"non-neg\")\n * .exhaustive();\n * sign(-1); // \"neg\"\n * sign(0); // \"non-neg\"\n * ```\n */\nexport function when<T, U extends T>(guard: (input: T) => input is U): (input: T) => input is U;\nexport function when<T>(guard: (input: T) => boolean): Pattern<T>;\nexport function when<T>(guard: (input: T) => boolean): Pattern<T> {\n return guard;\n}\n"]}
@@ -0,0 +1,247 @@
1
+ import type { ExhaustiveResult, ExhaustMatched } from "./exhaustive.js";
2
+ import type { Narrow, NarrowUnion } from "./narrow.js";
3
+ /**
4
+ * A pattern that matches a value of type `T`. One of:
5
+ *
6
+ * - **Literal** — a primitive (`"a"`, `42`, `true`) matched by `===`.
7
+ * - **Object pattern** — a `Partial<T>` of shallow key/value pairs that
8
+ * must all match by strict equality. Only available when `T` is an
9
+ * object type.
10
+ * - **Guard** — a function `(input: T) => boolean`. Type predicates
11
+ * (`(x): x is U`) narrow the handler input via {@link Narrow}.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * type Event = { kind: "click"; x: number; y: number } | { kind: "key"; code: string };
16
+ *
17
+ * const p1: Pattern<Event> = { kind: "click" }; // object pattern
18
+ * const p2: Pattern<Event> = (e) => e.kind === "key"; // guard
19
+ * ```
20
+ */
21
+ export type Pattern<T> = T | ObjectPattern<T> | PatternGuard<T>;
22
+ type ObjectPattern<T> = T extends object ? Partial<T> : never;
23
+ type PatternGuard<T> = (input: T) => boolean;
24
+ type Case<T, R> = {
25
+ readonly test: (input: T) => boolean;
26
+ readonly run: (input: T) => R;
27
+ };
28
+ declare const NO_INPUT: unique symbol;
29
+ type NoInput = typeof NO_INPUT;
30
+ type NextResult<Locked extends boolean, R, R2> = Locked extends true ? R : R | R2;
31
+ type HandlerReturn<Locked extends boolean, R, R2> = Locked extends true ? R : R2;
32
+ /**
33
+ * Fluent builder for {@link match}. Tracks the handled cases at the type
34
+ * level so `.exhaustive()` only compiles when every union member is covered.
35
+ *
36
+ * The `Locked` phantom flag (set via `.returnType<R>()`) constrains every
37
+ * subsequent handler to return `R`, useful when branch return-type
38
+ * inference would widen to a union narrower than the target slot
39
+ * (e.g. `ReactNode`, an API DTO).
40
+ *
41
+ * @example
42
+ * ```ts
43
+ * const describe = match<Event>()
44
+ * .with({ kind: "click" }, (e) => `click @ ${e.x},${e.y}`)
45
+ * .with({ kind: "key" }, (e) => `key ${e.code}`)
46
+ * .exhaustive();
47
+ * ```
48
+ */
49
+ export declare class MatchBuilder<T, R = never, HasInput extends boolean = false, Handled extends readonly unknown[] = [], Locked extends boolean = false> {
50
+ private readonly cases;
51
+ private readonly input;
52
+ /** Phantom tuple of handled cases — only its compile-time contents matter. */
53
+ readonly _handled: Handled;
54
+ /**
55
+ * Construct a builder directly. Prefer the {@link match} factory — the
56
+ * constructor exists so internal combinators can thread accumulated `cases`,
57
+ * the optional captured `input`, and the phantom `_handled` tuple.
58
+ */
59
+ constructor(cases: Case<T, unknown>[], input: T | NoInput,
60
+ /** Phantom tuple of handled cases — only its compile-time contents matter. */
61
+ _handled?: Handled);
62
+ /** Appends a runtime case plus a phantom `H` entry on the handled tuple. */
63
+ private addCase;
64
+ /**
65
+ * Add a case: when `input` satisfies `pattern`, run `handler` on the narrowed
66
+ * value. The pattern may be a literal, a shallow object pattern, or a guard
67
+ * ({@link Pattern}); a type-predicate guard narrows the handler input. Each
68
+ * `.with` advances compile-time exhaustiveness tracking so `.exhaustive()`
69
+ * knows which union members remain.
70
+ *
71
+ * @param pattern - literal, object pattern, or guard to test against
72
+ * @param handler - run on the narrowed input when the pattern matches
73
+ * @returns a builder with this case appended
74
+ *
75
+ * @example
76
+ * ```ts
77
+ * type Event =
78
+ * | { type: "message"; content: string }
79
+ * | { type: "error"; message: string }
80
+ * | { type: "done" };
81
+ *
82
+ * const summary = match({ type: "message", content: "hi" } as Event)
83
+ * .with({ type: "message" }, (e) => e.content) // e: { type: "message"; content: string }
84
+ * .with({ type: "error" }, (e) => e.message)
85
+ * .with({ type: "done" }, () => "—")
86
+ * .exhaustive(); // "hi"
87
+ * ```
88
+ */
89
+ with<const P extends Pattern<T>, R2>(pattern: P, handler: (input: Narrow<T, P>) => HandlerReturn<Locked, R, R2>): MatchBuilder<T, NextResult<Locked, R, R2>, HasInput, readonly [...Handled, ExhaustMatched<T, P>], Locked>;
90
+ /**
91
+ * One handler for several patterns (OR). The handler input is the union of
92
+ * the members narrowed by each pattern, and all of them are marked handled
93
+ * for exhaustiveness.
94
+ *
95
+ * @param patterns - patterns to test; any match runs `handler`
96
+ * @param handler - run on the union of narrowed members
97
+ * @returns a builder with the shared case appended
98
+ *
99
+ * @example
100
+ * ```ts
101
+ * type Job =
102
+ * | { kind: "queued"; id: string }
103
+ * | { kind: "running"; id: string }
104
+ * | { kind: "done"; ok: boolean };
105
+ *
106
+ * const status = match({ kind: "running", id: "j1" } as Job)
107
+ * .withOneOf([{ kind: "queued" }, { kind: "running" }], (j) => j.id) // j: queued | running
108
+ * .with({ kind: "done" }, (j) => (j.ok ? "yes" : "no"))
109
+ * .exhaustive(); // "j1"
110
+ * ```
111
+ */
112
+ withOneOf<const Ps extends readonly Pattern<T>[], R2>(patterns: Ps, handler: (input: NarrowUnion<T, Ps>) => HandlerReturn<Locked, R, R2>): MatchBuilder<T, NextResult<Locked, R, R2>, HasInput, readonly [...Handled, NarrowUnion<T, Ps>], Locked>;
113
+ /**
114
+ * {@link withOneOf} for exactly two patterns — sugar that avoids the array.
115
+ *
116
+ * @param pattern1 - first pattern
117
+ * @param pattern2 - second pattern
118
+ * @param handler - run on the union of the two narrowed members
119
+ * @returns a builder with the shared case appended
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * type Provider = "ollama" | "openrouter";
124
+ *
125
+ * const pick = match<Provider>()
126
+ * .withEither("ollama", "openrouter", (p) => p) // p: "ollama" | "openrouter"
127
+ * .exhaustive();
128
+ * pick("ollama"); // "ollama"
129
+ * ```
130
+ */
131
+ withEither<const P1 extends Pattern<T>, const P2 extends Pattern<T>, R2>(pattern1: P1, pattern2: P2, handler: (input: NarrowUnion<T, readonly [P1, P2]>) => HandlerReturn<Locked, R, R2>): MatchBuilder<T, NextResult<Locked, R, R2>, HasInput, readonly [...Handled, NarrowUnion<T, readonly [P1, P2]>], Locked>;
132
+ /**
133
+ * Lock the result type. All subsequent `.with()` handlers must return `R2`.
134
+ * Useful when branch return-type inference widens to a union narrower than
135
+ * the slot the match feeds into (e.g. `ReactNode`).
136
+ *
137
+ * Also the fix for literal-widening: when a handler returns an object whose
138
+ * field is a union literal, inference widens it (e.g. `{ mode: "native" }`
139
+ * becomes `{ mode: string }`, breaking a `Mode` union). Lock the target type
140
+ * instead of sprinkling `as const`:
141
+ *
142
+ * @example
143
+ * ```ts
144
+ * type State = { mode: "compat" | "native" };
145
+ * const next = match(flag)
146
+ * .returnType<State>() // handlers now checked against State
147
+ * .with("--native", () => ({ mode: "native" })) // no `as const` needed
148
+ * .otherwise(() => state);
149
+ * ```
150
+ */
151
+ returnType<R2>(): MatchBuilder<T, R2, HasInput, Handled, true>;
152
+ /**
153
+ * Run the accumulated cases now, without a compile-time exhaustiveness check.
154
+ * Uses the value passed to {@link match} (data-first), or the value passed to
155
+ * `run` (curried). Throws `"Non-exhaustive match"` if no case matches at
156
+ * runtime — prefer {@link exhaustive} when the input is a closed union, or
157
+ * {@link otherwise} for an explicit fallback.
158
+ *
159
+ * @param input - the value to match when the builder was created curried
160
+ * @returns the matched handler's result
161
+ * @throws if no case matches the input
162
+ *
163
+ * @example
164
+ * ```ts
165
+ * type Event = { type: "error"; message: string } | { type: "done" };
166
+ *
167
+ * const msg = match({ type: "error", message: "x" } as Event)
168
+ * .with({ type: "error" }, (e) => e.message)
169
+ * .run({ type: "error", message: "net" }); // "net"
170
+ * ```
171
+ */
172
+ run(...input: readonly [input: T] | readonly []): R;
173
+ /**
174
+ * Settle the match with a compile-time exhaustiveness guarantee. Only
175
+ * type-checks when every member of `T` has been handled; otherwise the return
176
+ * type becomes {@link NonExhaustiveError}, surfacing the missing cases as a
177
+ * type error. Returns the result directly when data-first ({@link match}
178
+ * called with a value), or a reusable `(input: T) => R` matcher when curried.
179
+ *
180
+ * @returns the result (data-first) or a matcher function (curried)
181
+ *
182
+ * @example
183
+ * ```ts
184
+ * type Event =
185
+ * | { type: "message"; content: string }
186
+ * | { type: "error"; message: string }
187
+ * | { type: "done" };
188
+ *
189
+ * // Curried — reusable matcher; omitting a `.with` would be a type error.
190
+ * const describe = match<Event>()
191
+ * .with({ type: "message" }, (e) => e.content)
192
+ * .with({ type: "error" }, (e) => `! ${e.message}`)
193
+ * .with({ type: "done" }, () => "done")
194
+ * .exhaustive();
195
+ * describe({ type: "done" }); // "done"
196
+ * ```
197
+ */
198
+ exhaustive(): ExhaustiveResult<T, Handled, R, HasInput>;
199
+ /**
200
+ * Settle the match with a catch-all fallback, dropping the exhaustiveness
201
+ * requirement — `handler` runs (on the full input type) when no case matched.
202
+ * Returns the result directly when data-first, or a reusable matcher when
203
+ * curried.
204
+ *
205
+ * @param handler - fallback run on the input when no prior case matched
206
+ * @returns the result (data-first) or a matcher function (curried)
207
+ *
208
+ * @example
209
+ * ```ts
210
+ * type Event =
211
+ * | { type: "message"; content: string }
212
+ * | { type: "error"; message: string }
213
+ * | { type: "done" };
214
+ *
215
+ * const len = match({ type: "done" } as Event)
216
+ * .with({ type: "message" }, (e) => e.content.length)
217
+ * .otherwise(() => -1); // -1
218
+ * ```
219
+ */
220
+ otherwise(handler: (input: T) => R): HasInput extends true ? R : (input: T) => R;
221
+ }
222
+ /**
223
+ * Start a pattern-matching expression. Two call shapes:
224
+ *
225
+ * - `match(value)` — data-first; subsequent `.exhaustive()` or
226
+ * `.otherwise(fn)` runs immediately against `value`.
227
+ * - `match<T>()` — curried; subsequent `.exhaustive()` returns a function
228
+ * `(value: T) => R` for use in `pipe(...)` or as a reusable matcher.
229
+ *
230
+ * @example
231
+ * ```ts
232
+ * // Data-first
233
+ * const out = match({ kind: "click", x: 1, y: 2 } as Event)
234
+ * .with({ kind: "click" }, (e) => e.x + e.y)
235
+ * .with({ kind: "key" }, () => -1)
236
+ * .exhaustive();
237
+ *
238
+ * // Curried — build a reusable matcher
239
+ * const describe = match<Event>()
240
+ * .with({ kind: "click" }, (e) => `click @ ${e.x},${e.y}`)
241
+ * .with({ kind: "key" }, (e) => `key ${e.code}`)
242
+ * .exhaustive();
243
+ * ```
244
+ */
245
+ export declare function match<T>(input: T): MatchBuilder<T, never, true>;
246
+ export declare function match<T = never>(): MatchBuilder<T, never, false>;
247
+ export {};