# rtt — Runtime Types for TypeScript

[![npm version](https://img.shields.io/npm/v/rtt.svg)](https://www.npmjs.com/package/rtt)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/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:

```ts
// ✅ 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:

```ts
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

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

### Type Narrowing

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

```ts
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:

```ts
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.

```ts
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:

```ts
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.

```ts
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.

```ts
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

```ts
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:

```ts
import {Brand} from 'rtt';

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

## Development

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

## License

[MIT](LICENSE) — © [Nizami](https://github.com/nizami)
