Skip to main content

literal

LiteralUnion, Exact, Narrow, Widen, and literal-vs-primitive guards.

import type {
  LiteralUnion, Exact, Widen, Narrow,
  IsLiteral, IsStringLiteral, IsNumericLiteral, IsBooleanLiteral, IsBigIntLiteral,
} from '@gentleduck/ttest/literal'

Literal-vs-primitive transforms and guards. Use these to author APIs that suggest typed values while staying permissive, lock generic inputs to exact shapes, or detect accidental widening in tests.

LiteralUnion

type LiteralUnion<L extends W, W> = L | (W & { _?: never })

Autocomplete-friendly literal union: the IDE suggests L, but the type still accepts any value of the wider type W. The phantom { _?: never } intersection prevents W from collapsing the union back to the primitive.

type Variant = LiteralUnion<'primary' | 'danger', string>
// autocomplete: 'primary' | 'danger'
// accepts:      any string

const a: Variant = 'primary'    // ok
const b: Variant = 'custom-x'   // ok — any string

Exact

type Exact<T, Shape>

Force T to match Shape exactly — no extra properties allowed. Useful for constructor configs that must be airtight.

type Config = { id: number }
function init<T>(c: Exact<T, Config>) { /* ... */ }

init({ id: 1 })             // ok
init({ id: 1, extra: 2 })   // Error — excess property `extra`

Widen

type Widen<T>

Widen a literal to its primitive type.

type a = Widen<'foo'>  // string
type b = Widen<42>     // number
type c = Widen<true>   // boolean
type d = Widen<10n>    // bigint

Edge case: Widen<string> is string (pass-through). Widen<'a' | 'b'> distributes to string | string = string.

Narrow

type Narrow<T>

Prevent inference from widening literal arguments. Use as a generic constraint when you want literals preserved in inferred types — equivalent to applying as const retroactively.

function defineRoles<T extends Narrow<T>>(roles: T): T { return roles }

const r = defineRoles(['admin', 'editor'])
//   ^? ['admin', 'editor']    not string[]

IsLiteral

type IsLiteral<T>

true if T is a literal (not its widened primitive). Combines string/number/boolean/bigint/symbol literal checks.

type a = IsLiteral<'foo'>   // true
type b = IsLiteral<42>      // true
type c = IsLiteral<true>    // true
type d = IsLiteral<string>  // false
type e = IsLiteral<number>  // false

IsStringLiteral

type IsStringLiteral<T>

true only for the literal form of string.

type a = IsStringLiteral<'foo'>    // true
type b = IsStringLiteral<string>   // false
type c = IsStringLiteral<'a' | 'b'> // true   (every member is a literal)

IsNumericLiteral

type IsNumericLiteral<T>

true only for the literal form of number.

type a = IsNumericLiteral<42>      // true
type b = IsNumericLiteral<number>  // false

IsBooleanLiteral

type IsBooleanLiteral<T>

true only for true or false exactly (not boolean).

type a = IsBooleanLiteral<true>     // true
type b = IsBooleanLiteral<false>    // true
type c = IsBooleanLiteral<boolean>  // false

IsBigIntLiteral

type IsBigIntLiteral<T>

true only for the literal form of bigint.

type a = IsBigIntLiteral<10n>     // true
type b = IsBigIntLiteral<bigint>  // false

Edge case across all Is*Literal helpers: any makes all of them return boolean (both branches). Guard with IsAny when authoring tests against suspicious surfaces.