Skip to main content

discriminated

Helpers for tagged unions sharing a literal discriminant key.

import type {
  NarrowByTag, OmitTag, TagsOf, PayloadOf, IsDiscriminated, Matchers,
} from '@gentleduck/ttest/discriminated'

Works on union types where every variant shares a kind / type / tag slot holding a literal.

NarrowByTag

type NarrowByTag<U, K extends keyof U, V>

Narrow U to the variant where U[K] === V.

type Shape =
  | { kind: 'circle'; r: number }
  | { kind: 'square'; side: number }

type Circle = NarrowByTag<Shape, 'kind', 'circle'>  // { kind: 'circle'; r: number }

OmitTag

type OmitTag<U, K extends keyof U>

Strip the tag property K from every variant of U.

type ShapePayload = OmitTag<Shape, 'kind'>
// { r: number } | { side: number }

TagsOf

type TagsOf<U, K extends keyof U>

Union of all tag values in slot K.

type Tags = TagsOf<Shape, 'kind'>  // 'circle' | 'square'

PayloadOf

type PayloadOf<U, K extends keyof U, V>

Tag-stripped variant whose tag at K equals V.

type CirclePayload = PayloadOf<Shape, 'kind', 'circle'>  // { r: number }

IsDiscriminated

type IsDiscriminated<U, K extends keyof U>

true if U has 2+ variants with distinct tag values at K.

Matchers

type Matchers<U, K extends keyof U, R> = {
  [V in U extends unknown ? U[K] & PropertyKey : never]: (variant: NarrowByTag<U, K, V>) => R
}

Pattern-match handler object — one method per tag value.

type ShapeMatchers = Matchers<Shape, 'kind', number>
// {
//   circle: (v: { kind: 'circle'; r: number }) => number
//   square: (v: { kind: 'square'; side: number }) => number
// }