import type { Exact, ValueOf } from "type-fest"; type UnaryFn = (shape: T) => R; // `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 T]: UnaryFn }; // 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 >; // 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; >, Handled>>( 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>; >, Handled>>( handlers: Handled, fallback: Fallback, ): UnaryFn | R>; } // oxlint-enable typescript/unified-signatures type HandlerMap = Record | undefined>; const dispatch = (handlers: HandlerMap, fallback?: UnaryFn) => (shape: string | number): unknown => ( handlers[shape] ?? fallback ?? (() => { throw new Error(`Unhandled shape: ${shape}`); }) )( // oxlint-disable-next-line typescript/no-unsafe-type-assertion shape as never, ); export const getMatcher = (): MatcherStrict => dispatch; export const getMatcherW = (): MatcherWidening => dispatch;