Skip to content

Ts

TypeScript type utilities and type-level programming helpers.

Provides comprehensive type-level utilities including type printing, static errors, type guards, simplification utilities, exact type matching, and type testing tools. Features conditional types, type transformations, and type-level assertions for advanced TypeScript patterns.

Import

typescript
import { Ts } from '@wollybeard/kit'
typescript
import * as Ts from '@wollybeard/kit/ts'

Namespaces

NamespaceDescription
KindHigher-kinded type utilities for type-level programming. Provides type-level functions and utilities for simulating higher-kinded types in TypeScript.
SimpleSignatureUtilities for working with the __simpleSignature phantom type pattern. Allows complex generic functions to provide simpler signatures for type inference.
Assert
InhabitanceType utilities for classifying types by their inhabitance in TypeScript's type lattice.
SimplifyType simplification utilities for flattening and expanding types. All functions automatically preserve globally registered types from KitLibrarySettings.Ts.PreserveTypes.
ErrError utilities for working with static type-level errors.
UnionUtilities for working with union types at the type level.
VariancePhantomPhantom type helpers for controlling type variance (covariance, contravariance, invariance, bivariance).
SENTINELUtilities for working with the SENTINEL type.

Type Printing

[T] Show

typescript
type Show<$Type> = `\`${Print<$Type>}\``

Like Print but adds additional styling to display the rendered type in a sentence.

Useful for type-level error messages where you want to clearly distinguish type names from surrounding text. Wraps the printed type with backticks () like inline code in Markdown.

$Type

  • The type to format and display

Examples:

typescript
type 
Message1
= `Expected ${
Show
<string>} but got ${
Show
<number>}`
// Result: "Expected `string` but got `number`" type
Message2
= `The type ${
Show
<'hello' | 'world'>} is not assignable`
// Result: "The type `'hello' | 'world'` is not assignable" // Using in error messages type
TypeError
<
Expected
,
Actual
> =
StaticError
<
`Type mismatch: expected ${
Show
<
Expected
>} but got ${
Show
<
Actual
>}`,
{
Expected
;
Actual
}
>

[T] ShowInTemplate

typescript
type ShowInTemplate<$Type> = `'${Print<$Type>}'`

Version of Show but uses single quotes instead of backticks.

This can be useful in template literal types where backticks would be rendered as "" which is not ideal for readability. Use this when the output will be used within another template literal type or when backticks cause display issues.

Note that when working with TS-level errors, if TS can instantiate all the types involved then the result will be a string, not a string literal type. So when working with TS-level errors, only reach for this variant of Show if you think there is likelihood that types won't be instantiated.

$Type

  • The type to format and display

Examples:

typescript
// When backticks would be escaped in output
type 
ErrorInTemplate
= `Error: ${
ShowInTemplate
<string>} is required`
// Result: "Error: 'string' is required" // Comparing Show vs ShowInTemplate type
WithShow
= `Type is ${
Show
<number>}`
// May display as: "Type is \`number\`" (escaped backticks) type
WithShowInTemplate
= `Type is ${
ShowInTemplate
<number>}`
// Displays as: "Type is 'number'" (cleaner)

[T] Print

typescript
type Print<$Type, $Fallback extends string | undefined = undefined> =
  // Language base category types
  IsAny<$Type> extends true ? 'any'
    : IsUnknown<$Type> extends true ? 'unknown'
    : IsNever<$Type> extends true ? 'never'
    // Special union type boolean which we display as boolean insead of true | false
    : [$Type] extends [boolean]
      ? ([boolean] extends [$Type] ? 'boolean' : `${$Type}`)
    // General unions types
    : Union.ToTuple<$Type> extends ArrMut.Any2OrMoreRO
      ? _PrintUnion<Union.ToTuple<$Type>>
    // Primitive and literal types
    : $Type extends true ? 'true'
    : $Type extends false ? 'false'
    : $Type extends void ? ($Type extends undefined ? 'undefined' : 'void')
    : $Type extends string ? (string extends $Type ? 'string' : `'${$Type}'`)
    : $Type extends number ? (number extends $Type ? 'number' : `${$Type}`)
    : $Type extends bigint ? (bigint extends $Type ? 'bigint' : `${$Type}n`)
    : $Type extends null ? 'null'
    : $Type extends undefined ? 'undefined'
    // User-provided fallback takes precedence if type is not a primitive
    : $Fallback extends string ? $Fallback
    // Common object types and specific generic patterns
    : $Type extends Promise<infer T> ? `Promise<${Print<T>}>`
    : $Type extends (infer T)[] ? `Array<${Print<T>}>`
    : $Type extends readonly (infer T)[] ? `ReadonlyArray<${Print<T>}>`
    : $Type extends Date ? 'Date'
    : $Type extends RegExp ? 'RegExp'
    //
    : $Type extends Function ? 'Function'
    : $Type extends symbol ? 'symbol'
    // General object fallback
    : $Type extends object ? 'object'
    // Ultimate fallback
    : '?'

Print a type as a readable string representation.

Type Utilities

[U] Interpolatable

typescript
type Interpolatable =
  | string
  | number
  | bigint
  | boolean
  | null
  | undefined
  | symbol
  | object
  | unknown
  | any
  | never

Types that TypeScript accepts being interpolated into a Template Literal Type.

These are the types that can be used within template literal types without causing a TypeScript error. When a value of one of these types is interpolated into a template literal type, TypeScript will properly convert it to its string representation.

Examples:

typescript
// All these types can be interpolated:
type 
Valid1
= `Value: ${string}`
type
Valid2
= `Count: ${number}`
type
Valid3
= `Flag: ${boolean}`
type
Valid4
= `ID: ${123n}`
// Example usage in conditional types: type
Stringify
<
T
extends
Interpolatable
> = `${
T
}`
type
Result1
=
Stringify
<42> // "42"
type
Result2
=
Stringify
<true> // "true"
type
Result3
=
Stringify
<'hello'> // "hello"

[U] Primitive

typescript
type Primitive = null | undefined | string | number | boolean | symbol | bigint

Matches any primitive value type.

Primitive values are the basic building blocks of JavaScript that are not objects. This includes all value types that are not Objects, Functions, or Arrays.

Examples:

typescript
type 
T1
=
Primitive
extends string ? true : false // true
type
T2
=
Primitive
extends {
x
: number } ? true : false // false
// Use in conditional types type
StripPrimitives
<
T
> =
T
extends
Primitive
? never :
T

[T] PrimitiveBrandLike

typescript
type PrimitiveBrandLike = { readonly [Brand.BrandTypeId]: any }

Structural pattern matching any Effect-branded primitive type.

This type matches primitives that have been branded using Effect's , by structurally checking for the presence of the BrandTypeId symbol property. It's used in KitLibrarySettings.Ts.PreserveTypes to prevent branded types from being expanded in type displays and error messages.

How it works:

Effect's branded types follow this pattern:

ts
type NonNegative = number & Brand.Brand<'NonNegative'>
// Which expands to:
// number & { readonly [BrandTypeId]: { readonly NonNegative: "NonNegative" } }

The check T extends { readonly [BrandTypeId]: any } structurally matches the brand part while plain primitives fail the check since they lack the symbol property.

Examples:

typescript
import type { 
Brand
} from 'effect'
type
NonNegative
= number &
Brand
.
Brand
<'NonNegative'>
type
Int
= number &
Brand
.
Brand
<'Int'>
// Branded types match type
Test1
=
NonNegative
extends
PrimitiveBrandLike
? true : false // true
type
Test2
=
Int
extends
PrimitiveBrandLike
? true : false // true
// Plain primitives don't match type
Test3
= number extends
PrimitiveBrandLike
? true : false // false
type
Test4
= string extends
PrimitiveBrandLike
? true : false // false

[T] WritableDeep

typescript
type WritableDeep<$T> =
  // Built-ins checked FIRST - handles branded types like `number & { [Brand]: true }`
  $T extends Primitive | void | Date | RegExp ? $T
    : $T extends (...args: any[]) => any ? $T // Functions pass through
    : $T extends readonly any[]
      ? { -readonly [i in keyof $T]: WritableDeep<$T[i]> } // Arrays/tuples
    : $T extends object ? { -readonly [k in keyof $T]: WritableDeep<$T[k]> } // Objects
    : $T

Recursively make all properties writable (removes readonly modifiers deeply).

Handles functions, primitives, built-ins, and branded types correctly by passing them through. Only recursively processes plain objects and tuples/arrays.

Unlike type-fest's WritableDeep, this implementation properly handles function types during TypeScript inference, preventing inference failures that result in unknown.

Built-in types (primitives, Date, RegExp, etc.) are checked FIRST to handle branded types like number & { [Brand]: true }, which extend both number and object.

$T

  • The type to recursively make writable

Examples:

typescript
// Primitives and built-ins pass through
type 
N
=
WritableDeep
<number> // number
type
D
=
WritableDeep
<Date> // Date
// Branded types pass through (checked before object) type
Branded
= number & { [brand]: true }
type
Result
=
WritableDeep
<
Branded
> // number & { [brand]: true }
// Functions pass through unchanged type
Fn
= (
x
: readonly string[]) => void
type
Result2
=
WritableDeep
<
Fn
> // (x: readonly string[]) => void
// Objects are recursively processed type
Obj
= { readonly
a
: { readonly
b
: number } }
type
Result3
=
WritableDeep
<
Obj
> // { a: { b: number } }
// Arrays/tuples are recursively processed type
Arr
= readonly [readonly string[], readonly number[]]
type
Result4
=
WritableDeep
<
Arr
> // [string[], number[]]

[T] StripReadonlyDeep

typescript
type StripReadonlyDeep<$T> = $T extends Function ? $T
  // TUPLE HANDLING: Must come before Array AND before GetPreservedTypes to preserve structure
  : $T extends readonly [...infer ___Elements]
    ? { -readonly [i in keyof ___Elements]: StripReadonlyDeep<___Elements[i]> }
  // Array handling (only matches non-tuple arrays now)
  : $T extends Array<infer __element__> ? Array<StripReadonlyDeep<__element__>>
  : $T extends ReadonlyArray<infer __element__>
    ? Array<StripReadonlyDeep<__element__>>
  // Preserve types from settings AFTER array/tuple handling (branded primitives, built-ins, user-registered)
  : $T extends GetPreservedTypes ? $T
  : $T extends object ?
      & { -readonly [k in keyof $T]: StripReadonlyDeep<$T[k]> }
      & unknown
  : $T

Recursively strip readonly modifiers from a type.

Strips readonly from objects, tuples, and arrays while recursing into nested structures. Uses inline simplification (& unknown) to avoid wrapper type names in error messages.

Automatically preserves types registered in KitLibrarySettings.Ts.PreserveTypes (including built-in types like Date, Error, Function, and branded primitives).

CRITICAL: Handles tuples BEFORE arrays to preserve tuple structure. Without tuple handling, [1, 2] would match Array<infer element> and widen to (1 | 2)[].

$T

  • The type to strip readonly from

Examples:

typescript
// Object with readonly properties
type 
ReadonlyObj
= { readonly
x
: number; readonly
y
: string }
type
Mutable
=
StripReadonlyDeep
<
ReadonlyObj
>
// { x: number; y: string } // Readonly tuple type
ReadonlyTuple
= readonly [1, 2, 3]
type
MutableTuple
=
StripReadonlyDeep
<
ReadonlyTuple
>
// [1, 2, 3] // Readonly array type
ReadonlyArr
=
ReadonlyArray
<number>
type
MutableArr
=
StripReadonlyDeep
<
ReadonlyArr
>
// Array<number> // Nested structures with branded types type
NonNegative
= number & Brand.
Brand
<'NonNegative'>
type
Nested
= { readonly
data
: readonly [
NonNegative
, 1, 2] }
type
NestedMutable
=
StripReadonlyDeep
<
Nested
>
// { data: [NonNegative, 1, 2] } - branded type preserved!

[T] SENTINEL

typescript
type SENTINEL = { readonly __kit_ts_sentinel__: unique symbol }

Sentinel type for detecting whether an optional type parameter was provided.

Use as default value for optional type parameters when you need to distinguish between "user explicitly provided a type" vs "using default/inferring".

Enables conditional behavior based on whether the caller provided an explicit type argument or is relying on inference/defaults.

Examples:

typescript
// Different behavior based on whether type arg provided
function 
process
<
$T
=
SENTINEL
>(...):
Ts
.SENTINEL.
Is
<
$T
> extends true
? // No type arg - infer from value : // Type arg provided - use it
typescript
// Real-world usage in assertion functions
type 
AssertFn
<
$Expected
,
$Actual
=
SENTINEL
> =
Ts
.SENTINEL.
Is
<
$Actual
> extends
true ? <
$actual
>(
value
:
$actual
) => void // Value mode
: void // Type-only mode

Utilities

[F] as

typescript
<$value>(value ?: unknown): $value

Parameters:

  • value - The value to cast (defaults to undefined)

Returns: The value cast to the specified type

Cast any value to a specific type for testing purposes. Useful for type-level testing where you need to create a value with a specific type.

$value

  • The type to cast to

Examples:

typescript
// Creating typed test values
const 
user
= as<{
id
: string;
name
: string }>({
id
: '1',
name
: 'Alice' })
// Testing type inference declare let
_
: any
const
result
= someFunction()
assertExtends<string>()(
_
as typeof
result
)

Utils

[T] TupleToLettered

typescript
type TupleToLettered<
  $Values extends readonly string[],
  $Prefix extends string = 'tip',
> = {
  [
    i in keyof $Values as i extends `${infer __n__ extends number}`
      ? `${$Prefix}_${Str.Char.LettersLower[__n__]}`
      : never
  ]: $Values[i]
}

Represents a type error that can be surfaced at the type level.

This is useful for providing more informative error messages directly in TypeScript's type checking, often used with conditional types or generic constraints. When TypeScript encounters this type, it will display the error information in a structured way.

$Message

  • A string literal type describing the error

$Context

  • An object type providing additional context about the error,

often including the types involved

$Hint

  • A string literal type providing a hint for resolving the error

Examples:

typescript
// Creating a custom type error
type 
RequireString
<
T
> =
T
extends string ?
T
:
StaticError
<
'Type must be a string', {
Received
:
T
},
'Consider using string or a string literal type' > type
Good
=
RequireString
<'hello'> // 'hello'
type
Bad
=
RequireString
<number> // StaticError<...>
typescript
// Using in function constraints
function 
processString
<
T
>(
value
:
T
extends string ?
T
:
StaticError
<
'Argument must be a string', {
ProvidedType
:
T
}
>, ): void { // Implementation }
processString
('hello') // OK
processString
(42) // Type error with custom message

Other

[T] IsAny

typescript
type IsAny<T> = 0 extends 1 & T ? true : false

Check if a type is any.

Uses the fact that any is the only type where 0 extends (1 & T) is true, since any absorbs all type operations including impossible intersections.

Examples:

typescript
type 
_
=
Ts
.Test.
Cases
<
Ts
.Test.
equal
<
IsAny
<any>, true>,
Ts
.Test.
equal
<
IsAny
<unknown>, false>,
Ts
.Test.
equal
<
IsAny
<string>, false>,
Ts
.Test.
equal
<
IsAny
<never>, false>
>

[T] IsNever

typescript
type IsNever<$Type> = [$Type] extends [never] ? true : false

Type utilities for detecting TypeScript edge case types: any, never, and unknown.

These utilities are useful for conditional type logic that needs to handle these special types differently.

[T] IsUnknown

typescript
type IsUnknown<T> = unknown extends T ? (IsAny<T> extends true ? false : true)
  : false

Check if a type is unknown.

Unknown is the top type

  • everything extends unknown (except any, which is special). So we check if unknown extends the type (only true for unknown and any), then exclude any using IsAny.

Examples:

typescript
type 
_
=
Ts
.Test.
Cases
<
Ts
.Test.
equal
<
IsUnknown
<unknown>, true>,
Ts
.Test.
equal
<
IsUnknown
<any>, false>,
Ts
.Test.
equal
<
IsUnknown
<string>, false>,
Ts
.Test.
equal
<
IsUnknown
<never>, false>
>

[T] ExtendsExact

typescript
type ExtendsExact<$Input, $Constraint> = $Input extends $Constraint
  ? $Constraint extends $Input ? $Input
  : never
  : never

Utilities for working with union types at the type level.

[T] NotExtends

typescript
type NotExtends<$A, $B> = [$A] extends [$B] ? false : true

Type-level utility that checks if a type does NOT extend another type.

Returns true if type A does not extend type B, false otherwise. Useful for conditional type logic where you need to check the absence of a type relationship.

$A

  • The type to check

$B

  • The type to check against

Examples:

typescript
type 
T1
=
NotExtends
<string, number> // true (string doesn't extend number)
type
T2
=
NotExtends
<'hello', string> // false ('hello' extends string)
type
T3
=
NotExtends
<42, number> // false (42 extends number)
type
T4
=
NotExtends
<{
a
: 1 }, {
b
: 2 }> // true (different properties)
typescript
// Using in conditional types for optional handling
type 
VarBuilderToType
<
$Type
,
$VarBuilder
> =
$VarBuilder
['required'] extends true
?
Exclude
<
$Type
, undefined>
:
NotExtends
<
$VarBuilder
['default'], undefined> extends true
?
$Type
| undefined
:
$Type
// If default is undefined, type is just $Type // If default is not undefined, type is $Type | undefined
typescript
// Checking for specific type exclusions
type 
SafeDivide
<
T
> =
NotExtends
<
T
, 0> extends true ? number
:
StaticError
<'Cannot divide by zero'>
type
Result1
=
SafeDivide
<5> // number
type
Result2
=
SafeDivide
<0> // StaticError<'Cannot divide by zero'>

[T] Writeable

typescript
type Writeable<$Object> = {
  -readonly [k in keyof $Object]: $Object[k]
}

Make all properties in an object mutable (removes readonly modifiers).

Examples:

typescript
type 
Readonly
= { readonly
x
: number; readonly
y
: string }
type
Mutable
=
Writeable
<
Readonly
> // { x: number; y: string }

[T] BooleanCase

typescript
type BooleanCase<$T extends boolean> = $T extends true ? 'true' : 'false'

Convert a boolean type to a string literal 'true' or 'false'. Useful for lookup table indexing.

Examples:

typescript
type 
T1
=
BooleanCase
<true> // 'true'
type
T2
=
BooleanCase
<false> // 'false'
// Using in lookup tables: type
Result
= {
true
: 'yes'
false
: 'no'
}[
BooleanCase
<
SomeCheck
<
T
>>]

[T] IntersectionIgnoreNeverOrAny

typescript
type IntersectionIgnoreNeverOrAny<$T> = IsAny<$T> extends true ? unknown
  : $T extends never ? unknown
  : $T

Intersection that ignores never and any.

[T] NeverOrAnyToUnknown

typescript
type NeverOrAnyToUnknown<$T> = IsAny<$T> extends true ? unknown
  : $T extends never ? unknown
  : $T

Convert never or any to unknown.

[U] Narrowable

typescript
type Narrowable = string | number | bigint | boolean | []

Any narrowable primitive type.

[T] AnyAndUnknownToNever

typescript
type AnyAndUnknownToNever<$T> = IsAny<$T> extends true ? never
  : IsUnknown<$T> extends true ? never
  : $T

Convert any and unknown to never.

[F] isTypeWith

typescript
<reference>(reference: reference): <valueGiven>(value: ValidateIsSupertype<reference, valueGiven>) => value is reference extends valueGiven ? reference : never

Parameters:

  • reference - The reference value to compare against

Returns: A type guard function that narrows to the reference type

Create a type guard that checks if a value equals a reference value.

Examples:

typescript
const 
isNull
=
Ts
.isTypeWith(null)
const
value
: string | null = getString()
if (
isNull
(
value
)) {
// value is narrowed to null }

[F] isntTypeWith

typescript
<reference>(reference: reference): <valueGiven>(value: ValidateIsSupertype<reference, valueGiven>) => value is reference extends valueGiven ? Exclude<valueGiven, reference> : never

Parameters:

  • reference - The reference value to compare against

Returns: A type guard function that narrows by excluding the reference type

Create a type guard that checks if a value does not equal a reference value.

Examples:

typescript
const 
isntNull
=
Ts
.isntTypeWith(null)
const
value
: string | null = getString()
if (
isntNull
(
value
)) {
// value is narrowed to string }