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.