TypeScript types vanish at runtime.
rttbrings them back — with zero dependencies and a tiny footprint.
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.
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| 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 |
npm install rtt
# or yarn add rtt / pnpm add rttThe 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}`);
}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!
}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'};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 |
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
}Low-level comparison between two $Type descriptors. Handles inheritance chains and generic type matching internally.
interface $Type<T = unknown> {
name: string;
type?: T;
parent?: $Type | null;
generics?: $Type[] | null;
}A structural branding utility for compile-time + runtime safety:
import {Brand} from 'rtt';
type UserId = Brand<'UserId'> & number;npm install
npm test # Run vitest suite
npm run build # Build with Vite + dts (generates dist/)