Skip to content

Repository files navigation

rtt — Runtime Types for TypeScript

npm version License: MIT

TypeScript types vanish at runtime. rtt brings them back — with zero dependencies and a tiny footprint.


The Problem

When TypeScript compiles to JavaScript, all type information is stripped away. This creates a frustrating gap:

// ✅ At compile time — TypeScript knows this is a Dog
const dog: Dog = { name: 'Rover', breed: 'Labrador' };

// ❌ At runtime — it's just a plain object. No type info remains.
JSON.parse('{"name":"Rover","breed":"Labrador"}') // what IS this?

Without runtime type information, you're forced into fragile workarounds: manual type strings, instanceof checks that don't survive serialization, or heavy validation libraries like Zod for every use case.

The Solution

rtt lets you define first-class type descriptors that carry both runtime identity and compile-time TypeScript types — enabling inheritance-aware runtime type checking with a single function call:

import {$type, is} from 'rtt';

// 1️⃣ Define your type hierarchy
const $Animal = $type<Animal>('app.Animal');
const $Dog    = $type<Dog>('app.Dog', $Animal);
const $Cat    = $type<Cat>('app.Cat', $Animal);

export type Animal = {$type: $Type; name: string};
export type Dog  = Animal & { breed: string };
export type Cat  = Animal & { lives: number };

// 2️⃣ Create typed objects with descriptor metadata
const rover: Dog    = {$type: $Dog, name: 'Rover', breed: 'Labrador'};
const whiskers: Cat = {$type: $Cat, name: 'Whiskers', lives: 9};

// 3️⃣ Check types at runtime
is(rover, $Dog);       // ✅ true
is(rover, $Animal);    // ✅ true — inheritance works!
is(whiskers, $Dog);    // ❌ false

Features

Feature Description
🏗️ Type Hierarchies Define parent-child relationships and check them at runtime
🔍 Runtime Introspection Know exactly what type an object is, even after JSON round-trips
Zero Dependencies No bloat — just two functions and a lightweight interface
📦 Tiny Footprint Minimal bundle size, tree-shakeable ESM exports
🔒 Type Narrowing is() returns a TypeScript type guard for safe downstream access
🧬 Generic Type Support Compare parameterized types with their arguments
🏷️ Branded Types Optional structural branding for compile-time + runtime safety

Quick Start

Installation

npm install rtt
# or yarn add rtt / pnpm add rtt

Type Narrowing

The is() function is a type guard — TypeScript narrows the type automatically:

const unknownUser: unknown = {$type: $User, name: 'John'};

if (is(unknownUser, $User)) {
  // ✅ TypeScript knows user has 'name'
  console.log(`Hello, ${unknownUser.name}`);
}

Generic Types

Type descriptors work with generic types too — pass the inner type as a generics argument:

import {$type, is} from 'rtt';

// Define base types
const $User = $type<User>('app.User');
const $List = <T>($generic: $Type<T>) => $type<List<T>>('app.List', undefined, [$generic]);

export type User = {$type: $Type; name: string};
export type List<T> = {$type: $Type; items: T[]};

// Tag objects
const user: User    = {$type: $User, name: 'John'};
const userList: List<User> = {$type: $List($User), items: [user]};

// Runtime checks preserve generic types
const unknownUser: unknown = user;
const unknownList: unknown = userList;

if (is(unknownUser, $User)) {
  console.log(unknownUser.name); // ✅ ok
}

if (is(unknownList, $UserList)) {
  console.log(unknownList.items[0].name); // ✅ ok — generic preserved!
}

API Reference

$type<T>(name, parent?, generics?)$Type<T>

Creates a named type descriptor that can be composed into hierarchies. The generic parameter T connects the runtime descriptor to your TypeScript type.

const $Animal = $type<Animal>('app.Animal');
const $Dog    = $type<Dog>('app.Dog', $Animal);
Param Type Description
name string Unique type identifier (e.g. 'app.Dog')
parent? $Type | null Parent type for inheritance chains
generics? $Type[] Generic type arguments to compare

To tag a runtime object with a descriptor, spread it explicitly:

const rover: Dog = {$type: $Dog, name: 'Rover', breed: 'Labrador'};

$nominal<I, P>(id, parent?, generics?)<T>()$Nominal<I, T & P>

Creates a branded nominal type factory that produces values carrying $type metadata automatically.

const $UserId = $nominal('app.UserId')<{id: number}>();
const user = $UserId({id: 42});
Param Type Description
id string Unique nominal identifier
parent? $Nominal<string, P> | null Parent nominal type for inheritance chains
generics? $Type[] Generic type arguments to compare

is<T>(value, type)value is T

Runtime type guard. Returns true if value.$type matches type or any of its ancestors in the hierarchy.

if (is(rover, $Dog)) {
  // TypeScript narrows: rover is now typed as Dog
  console.log(rover.name); // ✅ safe
}

isType(a, b)boolean

Low-level comparison between two $Type descriptors. Handles inheritance chains and generic type matching internally.

$Type<T> Interface

interface $Type<T = unknown> {
  name: string;
  type?: T;
  parent?: $Type | null;
  generics?: $Type[] | null;
}

Brand<T> Type

A structural branding utility for compile-time + runtime safety:

import {Brand} from 'rtt';

type UserId = Brand<'UserId'> & number;

Development

npm install
npm test        # Run vitest suite
npm run build   # Build with Vite + dts (generates dist/)

License

MIT — © Nizami

About

Runtime Typescript Types

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages