import type { Exact, ValueOf } from "type-fest"; type UnaryFn = (shape: T) => R; // The primitive universe a matcher can discriminate. `boolean` is admitted as // the pair `true | false`; see README § Caveats for the unsupported members. type Matchable = string | number | boolean | null | undefined; // `boolean`, `null` and `undefined` cannot be property keys, so a mapped type // over the universe keys each non-key member by its stringification. `Param` // inverts that projection, so a handler callback still receives the *real* // member (`true`, not `"true"`) — see README § Caveats for the limits. type PatternKey = T extends boolean ? T extends true ? "true" : "false" : T extends null ? "null" : T extends undefined ? "undefined" : T; type PatternParam = K extends "true" ? true : K extends "false" ? false : K extends "null" ? null : K extends "undefined" ? undefined : K; // `Extract` drops optional handlers (`undefined`) so `PatternReturns` also works // when `P`'s constraint has optional keys. type PatternReturns

= ReturnType< Extract, (...args: never[]) => unknown> >; type Handlers = { [K in PatternKey]: UnaryFn, R>; }; // The fallback is a *second argument*, not a property of the handler map, // because its parameter is the remainder `Exclude` and TypeScript // fixes a property's contextual type before it infers its sibling keys. A later // argument, by contrast, is contextually typed from inference on an earlier // one, so the split is what makes the remainder expressible at all. // See development/library.md. type Fallback = UnaryFn< Exclude>, R >; // A fallback is redundant once the handler map covers `T`. The guard is folded // into `Handled`'s own (self-referential) constraint so it is checked *after* // inference; a conditional in the fallback's parameter type is evaluated while // `Handled` is still its constraint and would reject context-sensitive partial // maps. That placement also fixes where the diagnostic lands: the constraint // failure is reported on the argument that inferred `Handled` (the handler // map), so the required property is spelled as the message instead of relying // on its position. See development/library.md. interface RedundantFallback { readonly "every case is already handled, so the fallback is redundant": never; } type MustBePartial = PatternKey extends keyof Handled ? RedundantFallback : unknown; // TypeScript does not apply the excess-property check to a generic constraint, // so `Exact` restores it for the generic forms: a handler map can otherwise // carry keys outside `T`. // oxlint-disable typescript/unified-signatures // Strict returns: one common `R`. Overload order is load-bearing: // #1 Handlers (first) -> the exhaustive form and the autocomplete popup // #2 Fallback (last) -> accepts a partial handler map plus a fallback interface MatcherStrict { (handlers: Handlers): UnaryFn; < R, Handled extends Exact>, Handled> & MustBePartial, >( handlers: Handled & Partial>, fallback: Fallback, ): UnaryFn; } // oxlint-enable typescript/unified-signatures // Widened returns: the union of every handler's return type. `P` is inferred // from the whole handler map, whose closed constraint supplies the // contextual/autocomplete type. // oxlint-disable typescript/unified-signatures interface MatcherWidening {

, P>>( handlers: P, ): UnaryFn>; < R, Handled extends Exact>, Handled> & MustBePartial, >( handlers: Handled, fallback: Fallback, ): UnaryFn | R>; } // oxlint-enable typescript/unified-signatures type HandlerMap = Record | undefined>; const dispatch = (handlers: HandlerMap, fallback?: UnaryFn) => (shape: Matchable): unknown => // `handlers[true]` already coerces to the `"true"` property at // runtime; `String` is here only because TypeScript forbids // indexing with `boolean`/`null`/`undefined` (TS2538). ( handlers[String(shape)] ?? fallback ?? (() => { throw new Error(`Unhandled shape: ${String(shape)}`); }) )( // oxlint-disable-next-line typescript/no-unsafe-type-assertion shape as never, ); export const getMatcher = (): MatcherStrict => dispatch; export const getMatcherW = (): MatcherWidening => dispatch;