@onrails/pattern 0.3.5 → 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/DESIGN.md +4 -0
- package/README.md +4 -0
- package/dist/index.cjs +43 -29
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +43 -29
- package/dist/index.js.map +1 -1
- package/dist/match.d.cts +1 -1
- package/dist/match.d.ts +1 -1
- package/dist/tag.cjs +1 -2
- package/dist/tag.cjs.map +1 -1
- package/dist/tag.d.cts +6 -3
- package/dist/tag.d.ts +6 -3
- package/dist/tag.js +1 -2
- package/dist/tag.js.map +1 -1
- package/package.json +2 -2
package/DESIGN.md
CHANGED
|
@@ -9,6 +9,10 @@ Lightweight matching for **owned** tagged unions and finite domain states. Inspi
|
|
|
9
9
|
- **`matchTag`** for `_tag` unions (`Ok` / `Err`, `Some` / `None`, domain ADTs)
|
|
10
10
|
- No `P.*` wildcard zoo in v1 — use `when(guard)` or `matchTag`
|
|
11
11
|
|
|
12
|
+
## Builder lifetime
|
|
13
|
+
|
|
14
|
+
`MatchBuilder` is **linear-only**: each `.with` / `.withOneOf` / `.returnType` mutates the same case list in place. Do not fork an intermediate builder into two chains — share a hoisted curried matcher or build two full matchers instead. Patterns are compiled once at `.with` time (single-key discriminants skip per-test `Object.keys`).
|
|
15
|
+
|
|
12
16
|
## Handler narrowing
|
|
13
17
|
|
|
14
18
|
`.with(pattern, handler)` narrows the handler's input via `Narrow<T, P>`:
|
package/README.md
CHANGED
|
@@ -83,4 +83,8 @@ const show = <T, E>(r: Result<T, E>) =>
|
|
|
83
83
|
| `@onrails/pattern` | `match`, `when`, `assertNever`, `MatchBuilder` |
|
|
84
84
|
| `@onrails/pattern/tag` | `matchTag` |
|
|
85
85
|
|
|
86
|
+
## Benchmarks
|
|
87
|
+
|
|
88
|
+
Runtime + fair import-graph size vs `ts-pattern` (overlapping shallow surface only): see [`../bench/RESULTS.md`](../bench/RESULTS.md). Regenerated with `bun run bench:md` from the repo root.
|
|
89
|
+
|
|
86
90
|
See [DESIGN.md](./DESIGN.md).
|
package/dist/index.cjs
CHANGED
|
@@ -8,24 +8,34 @@ var assertNever = (value, message = "Unreachable") => {
|
|
|
8
8
|
// src/match.ts
|
|
9
9
|
var isGuard = (pattern) => typeof pattern === "function";
|
|
10
10
|
var isPatternObject = (pattern) => typeof pattern === "object" && pattern !== null;
|
|
11
|
-
var
|
|
12
|
-
if (isGuard(pattern))
|
|
13
|
-
return pattern(input);
|
|
14
|
-
}
|
|
11
|
+
var compileTest = (pattern) => {
|
|
12
|
+
if (isGuard(pattern)) return pattern;
|
|
15
13
|
if (!isPatternObject(pattern)) {
|
|
16
|
-
return input === pattern;
|
|
17
|
-
}
|
|
18
|
-
if (typeof input !== "object" || input === null) {
|
|
19
|
-
return false;
|
|
14
|
+
return (input) => input === pattern;
|
|
20
15
|
}
|
|
21
|
-
const record = input;
|
|
22
16
|
const patternRecord = pattern;
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
17
|
+
const keys = Object.keys(patternRecord);
|
|
18
|
+
if (keys.length === 0) {
|
|
19
|
+
return (input) => typeof input === "object" && input !== null;
|
|
20
|
+
}
|
|
21
|
+
if (keys.length === 1) {
|
|
22
|
+
const key = keys[0];
|
|
23
|
+
const expected = patternRecord[key];
|
|
24
|
+
return (input) => {
|
|
25
|
+
if (typeof input !== "object" || input === null) return false;
|
|
26
|
+
return input[key] === expected;
|
|
27
|
+
};
|
|
27
28
|
}
|
|
28
|
-
|
|
29
|
+
const pairs = keys.map((k) => [k, patternRecord[k]]);
|
|
30
|
+
return (input) => {
|
|
31
|
+
if (typeof input !== "object" || input === null) return false;
|
|
32
|
+
const record = input;
|
|
33
|
+
for (let i = 0; i < pairs.length; i++) {
|
|
34
|
+
const pair = pairs[i];
|
|
35
|
+
if (record[pair[0]] !== pair[1]) return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
29
39
|
};
|
|
30
40
|
var NO_INPUT = /* @__PURE__ */ Symbol("@onrails/pattern/no-input");
|
|
31
41
|
var findCase = (input, cases) => {
|
|
@@ -35,11 +45,19 @@ var findCase = (input, cases) => {
|
|
|
35
45
|
}
|
|
36
46
|
return null;
|
|
37
47
|
};
|
|
38
|
-
var caseForPatterns = (patterns, handler) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
var caseForPatterns = (patterns, handler) => {
|
|
49
|
+
const tests = patterns.map(compileTest);
|
|
50
|
+
return {
|
|
51
|
+
test: (input) => {
|
|
52
|
+
for (let i = 0; i < tests.length; i++) {
|
|
53
|
+
if (tests[i](input)) return true;
|
|
54
|
+
}
|
|
55
|
+
return false;
|
|
56
|
+
},
|
|
57
|
+
run: handler
|
|
58
|
+
};
|
|
59
|
+
};
|
|
60
|
+
var MatchBuilder = class {
|
|
43
61
|
/**
|
|
44
62
|
* Construct a builder directly. Prefer the {@link match} factory — the
|
|
45
63
|
* constructor exists so internal combinators can thread accumulated `cases`,
|
|
@@ -55,12 +73,8 @@ var MatchBuilder = class _MatchBuilder {
|
|
|
55
73
|
_handled;
|
|
56
74
|
/** Appends a runtime case plus a phantom `H` entry on the handled tuple. */
|
|
57
75
|
addCase(next) {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
this.input,
|
|
61
|
-
// Safe: _handled is a phantom tuple — only its type-level contents matter.
|
|
62
|
-
this._handled
|
|
63
|
-
);
|
|
76
|
+
this.cases.push(next);
|
|
77
|
+
return this;
|
|
64
78
|
}
|
|
65
79
|
/**
|
|
66
80
|
* Add a case: when `input` satisfies `pattern`, run `handler` on the narrowed
|
|
@@ -89,7 +103,8 @@ var MatchBuilder = class _MatchBuilder {
|
|
|
89
103
|
*/
|
|
90
104
|
with(pattern, handler) {
|
|
91
105
|
return this.addCase({
|
|
92
|
-
|
|
106
|
+
// Safe: P extends Pattern<T>; compiled predicate accepts the wider input T.
|
|
107
|
+
test: compileTest(pattern),
|
|
93
108
|
run: handler
|
|
94
109
|
});
|
|
95
110
|
}
|
|
@@ -161,7 +176,7 @@ var MatchBuilder = class _MatchBuilder {
|
|
|
161
176
|
* ```
|
|
162
177
|
*/
|
|
163
178
|
returnType() {
|
|
164
|
-
return
|
|
179
|
+
return this;
|
|
165
180
|
}
|
|
166
181
|
/**
|
|
167
182
|
* Run the accumulated cases now, without a compile-time exhaustiveness check.
|
|
@@ -257,8 +272,7 @@ function match(...input) {
|
|
|
257
272
|
|
|
258
273
|
// src/tag.ts
|
|
259
274
|
var matchTag = (value, handlers) => {
|
|
260
|
-
const
|
|
261
|
-
const handler = handlers[tag];
|
|
275
|
+
const handler = handlers[value._tag];
|
|
262
276
|
return handler(value);
|
|
263
277
|
};
|
|
264
278
|
|
package/dist/index.cjs.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.cjs","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.cjs","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"]}
|
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
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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"]}
|
package/dist/match.d.cts
CHANGED
|
@@ -56,7 +56,7 @@ export declare class MatchBuilder<T, R = never, HasInput extends boolean = false
|
|
|
56
56
|
* constructor exists so internal combinators can thread accumulated `cases`,
|
|
57
57
|
* the optional captured `input`, and the phantom `_handled` tuple.
|
|
58
58
|
*/
|
|
59
|
-
constructor(cases:
|
|
59
|
+
constructor(cases: Case<T, unknown>[], input: T | NoInput,
|
|
60
60
|
/** Phantom tuple of handled cases — only its compile-time contents matter. */
|
|
61
61
|
_handled?: Handled);
|
|
62
62
|
/** Appends a runtime case plus a phantom `H` entry on the handled tuple. */
|
package/dist/match.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export declare class MatchBuilder<T, R = never, HasInput extends boolean = false
|
|
|
56
56
|
* constructor exists so internal combinators can thread accumulated `cases`,
|
|
57
57
|
* the optional captured `input`, and the phantom `_handled` tuple.
|
|
58
58
|
*/
|
|
59
|
-
constructor(cases:
|
|
59
|
+
constructor(cases: Case<T, unknown>[], input: T | NoInput,
|
|
60
60
|
/** Phantom tuple of handled cases — only its compile-time contents matter. */
|
|
61
61
|
_handled?: Handled);
|
|
62
62
|
/** Appends a runtime case plus a phantom `H` entry on the handled tuple. */
|
package/dist/tag.cjs
CHANGED
package/dist/tag.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tag.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"sources":["../src/tag.ts"],"names":[],"mappings":";;;AAkCO,IAAM,QAAA,GAAW,CACtB,KAAA,EAEA,QAAA,KACsB;AACtB,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,KAAA,CAAM,IAAgB,CAAA;AAC/C,EAAA,OAAO,QAAQ,KAAK,CAAA;AACtB","file":"tag.cjs","sourcesContent":["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"]}
|
package/dist/tag.d.cts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
type Tagged = {
|
|
2
2
|
readonly _tag: string;
|
|
3
3
|
};
|
|
4
|
-
type TagHandlers<T extends Tagged
|
|
4
|
+
type TagHandlers<T extends Tagged> = {
|
|
5
5
|
[K in T["_tag"]]: (value: Extract<T, {
|
|
6
6
|
_tag: K;
|
|
7
|
-
}>) =>
|
|
7
|
+
}>) => unknown;
|
|
8
8
|
};
|
|
9
|
+
type HandlerResult<Hs> = {
|
|
10
|
+
[K in keyof Hs]: Hs[K] extends (...args: never[]) => infer U ? U : never;
|
|
11
|
+
}[keyof Hs];
|
|
9
12
|
/**
|
|
10
13
|
* Exhaustive match on `_tag` — for `@onrails/result` / `@onrails/maybe` style
|
|
11
14
|
* unions. The `handlers` object must supply one branch per `_tag`, and each
|
|
@@ -28,5 +31,5 @@ type TagHandlers<T extends Tagged, R> = {
|
|
|
28
31
|
* label(err("x")); // "err:x"
|
|
29
32
|
* ```
|
|
30
33
|
*/
|
|
31
|
-
export declare const matchTag: <T extends Tagged,
|
|
34
|
+
export declare const matchTag: <T extends Tagged, Hs extends TagHandlers<T>>(value: T, handlers: Hs & { [K in Exclude<keyof Hs, T["_tag"]>]: never; }) => HandlerResult<Hs>;
|
|
32
35
|
export {};
|
package/dist/tag.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
type Tagged = {
|
|
2
2
|
readonly _tag: string;
|
|
3
3
|
};
|
|
4
|
-
type TagHandlers<T extends Tagged
|
|
4
|
+
type TagHandlers<T extends Tagged> = {
|
|
5
5
|
[K in T["_tag"]]: (value: Extract<T, {
|
|
6
6
|
_tag: K;
|
|
7
|
-
}>) =>
|
|
7
|
+
}>) => unknown;
|
|
8
8
|
};
|
|
9
|
+
type HandlerResult<Hs> = {
|
|
10
|
+
[K in keyof Hs]: Hs[K] extends (...args: never[]) => infer U ? U : never;
|
|
11
|
+
}[keyof Hs];
|
|
9
12
|
/**
|
|
10
13
|
* Exhaustive match on `_tag` — for `@onrails/result` / `@onrails/maybe` style
|
|
11
14
|
* unions. The `handlers` object must supply one branch per `_tag`, and each
|
|
@@ -28,5 +31,5 @@ type TagHandlers<T extends Tagged, R> = {
|
|
|
28
31
|
* label(err("x")); // "err:x"
|
|
29
32
|
* ```
|
|
30
33
|
*/
|
|
31
|
-
export declare const matchTag: <T extends Tagged,
|
|
34
|
+
export declare const matchTag: <T extends Tagged, Hs extends TagHandlers<T>>(value: T, handlers: Hs & { [K in Exclude<keyof Hs, T["_tag"]>]: never; }) => HandlerResult<Hs>;
|
|
32
35
|
export {};
|
package/dist/tag.js
CHANGED
package/dist/tag.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/tag.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../src/tag.ts"],"names":[],"mappings":";AAkCO,IAAM,QAAA,GAAW,CACtB,KAAA,EAEA,QAAA,KACsB;AACtB,EAAA,MAAM,OAAA,GAAU,QAAA,CAAS,KAAA,CAAM,IAAgB,CAAA;AAC/C,EAAA,OAAO,QAAQ,KAAK,CAAA;AACtB","file":"tag.js","sourcesContent":["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"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onrails/pattern",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Lightweight exhaustive matching for owned tagged unions — ts-pattern-shaped DX, tree-shakeable",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Alan R. Soares",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"prepublishOnly": "bun run build"
|
|
73
73
|
},
|
|
74
74
|
"devDependencies": {
|
|
75
|
-
"@onrails/result": "0.4.
|
|
75
|
+
"@onrails/result": "0.4.2",
|
|
76
76
|
"ts-expect": "^1.3.0",
|
|
77
77
|
"tsup": "^8.5.1"
|
|
78
78
|
}
|