rtt 1.0.2 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,54 +1,178 @@
1
- # rtt
1
+ # rtt — Runtime Types for TypeScript
2
2
 
3
- Runtime Typescript Types
3
+ [![npm version](https://img.shields.io/npm/v/rtt.svg)](https://www.npmjs.com/package/rtt)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
4
5
 
5
- ## Overview
6
+ > **TypeScript types vanish at runtime.** `rtt` brings them back — with zero dependencies and a tiny footprint.
6
7
 
7
- This package provides a way to store type information in objects and check types at runtime in TypeScript. It is useful for scenarios where you need runtime type introspection, such as serialization, validation, or building type-safe APIs.
8
+ ---
9
+
10
+ ## The Problem
11
+
12
+ When TypeScript compiles to JavaScript, all type information is stripped away. This creates a frustrating gap:
13
+
14
+ ```ts
15
+ // ✅ At compile time — TypeScript knows this is a Dog
16
+ const dog: Dog = { name: 'Rover', breed: 'Labrador' };
17
+
18
+ // ❌ At runtime — it's just a plain object. No type info remains.
19
+ JSON.parse('{"name":"Rover","breed":"Labrador"}') // what IS this?
20
+ ```
21
+
22
+ 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.
23
+
24
+ ## The Solution
25
+
26
+ `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:
27
+
28
+ ```ts
29
+ import {$type, is} from 'rtt';
30
+
31
+ // 1️⃣ Define your type hierarchy
32
+ const $Animal = $type<Animal>('app.Animal');
33
+ const $Dog = $type<Dog>('app.Dog', $Animal);
34
+ const $Cat = $type<Cat>('app.Cat', $Animal);
35
+
36
+ export type Animal = {$type: $Type; name: string};
37
+ export type Dog = Animal & { breed: string };
38
+ export type Cat = Animal & { lives: number };
39
+
40
+ // 2️⃣ Create typed objects
41
+ const rover: Dog = {$type: $Dog, name: 'Rover', breed: 'Labrador'};
42
+ const whiskers: Cat = {$type: $Cat, name: 'Whiskers', lives: 9};
43
+
44
+ // 3️⃣ Check types at runtime
45
+ is(rover, $Dog); // ✅ true
46
+ is(rover, $Animal); // ✅ true — inheritance works!
47
+ is(whiskers, $Dog); // ❌ false
48
+ ```
8
49
 
9
50
  ## Features
10
51
 
11
- - Attach type metadata to objects.
12
- - Check if an object is of a specific type at runtime using the `is` function.
13
- - Support for type hierarchies and generics.
52
+ | Feature | Description |
53
+ |---|---|
54
+ | 🏗️ **Type Hierarchies** | Define parent-child relationships and check them at runtime |
55
+ | 🔍 **Runtime Introspection** | Know exactly what type an object is, even after JSON round-trips |
56
+ | ⚡ **Zero Dependencies** | No bloat — just two functions and a lightweight interface |
57
+ | 📦 **Tiny Footprint** | Minimal bundle size, tree-shakeable ESM exports |
58
+ | 🔒 **Type Narrowing** | `is()` returns a TypeScript type guard for safe downstream access |
59
+ | 🧬 **Generic Type Support** | Compare parameterized types with their arguments |
60
+ | 🏷️ **Branded Types** | Optional structural branding for compile-time + runtime safety |
61
+
62
+ ## Quick Start
14
63
 
15
- ## Installation
64
+ ### Installation
16
65
 
17
66
  ```sh
18
67
  npm install rtt
68
+ # or yarn add rtt / pnpm add rtt
19
69
  ```
20
70
 
21
- ## Usage
71
+ ### Type Narrowing
72
+
73
+ The `is()` function is a **type guard** — TypeScript narrows the type automatically:
74
+
75
+ ```ts
76
+ const unknownUser: unknown = {$type: $User, name: 'John'};
77
+
78
+ if (is(unknownUser, $User)) {
79
+ // ✅ TypeScript knows user has 'name'
80
+ console.log(`Hello, ${unknownUser.name}`);
81
+ }
82
+ ```
83
+
84
+ ### Generic Types
85
+
86
+ Type descriptors work with generic types too — pass the inner type as a `generics` argument:
22
87
 
23
- ```typescript
24
- import {$NewType, is, Model} from 'rtt';
88
+ ```ts
89
+ import {$type, is} from 'rtt';
25
90
 
26
- // Define types
27
- const $A = () => $NewType('MyPackage', 'A');
28
- const $B = () => $NewType('MyPackage', 'B', $A());
91
+ // Define base types
92
+ const $User = $type<User>('app.User');
93
+ const $List = <T>($generic: $Type<T>) => $type<List<T>>('app.List', undefined, [$generic]);
29
94
 
30
- // Create a model instance
31
- const model: Model = {
32
- $type: $B(),
33
- };
95
+ export type User = {$type: $Type; name: string};
96
+ export type List<T> = {$type: $Type; items: T[]};
34
97
 
35
- // Runtime type checks
36
- is(model, $B()); // true
37
- is(model, $A()); // true (because B extends A)
98
+ // Tag objects
99
+ const user: User = {$type: $User, name: 'John'};
100
+ const userList: List<User> = {$type: $List($User), items: [user]};
101
+
102
+ // Runtime checks preserve generic types
103
+ const unknownUser: unknown = user;
104
+ const unknownList: unknown = userList;
105
+
106
+ if (is(unknownUser, $User)) {
107
+ console.log(unknownUser.name); // ✅ ok
108
+ }
109
+
110
+ if (is(unknownList, $UserList)) {
111
+ console.log(unknownList.items[0].name); // ✅ ok — generic preserved!
112
+ }
38
113
  ```
39
114
 
40
- ## API
115
+ ## API Reference
41
116
 
42
- ### `$NewType(packageName: string, typeName: string, parent?, generics?)`
117
+ ### `$type<T>(name, parent?, generics?)` → `$Type<T>`
43
118
 
44
- Creates a new runtime type.
119
+ Creates a named type descriptor that can be composed into hierarchies. The generic parameter `T` connects the runtime descriptor to your TypeScript type.
45
120
 
46
- ### `is(model: any, type: $Type): boolean`
121
+ ```ts
122
+ const $Animal = $type<Animal>('app.Animal');
123
+ const $Dog = $type<Dog>('app.Dog', $Animal);
124
+ ```
47
125
 
48
- Checks if the given model is of the specified type (or its parent).
126
+ | Param | Type | Description |
127
+ |---|---|---|
128
+ | `name` | `string` | Unique type identifier (e.g. `'app.Dog'`) |
129
+ | `parent?` | `$Type \| null` | Parent type for inheritance chains |
130
+ | `generics?` | `$Type[]` | Generic type arguments to compare |
49
131
 
50
- ### `Model`
132
+ ### `is<T>(value, type)` → `value is T`
51
133
 
52
- Interface for objects with runtime type information.
134
+ Runtime type guard. Returns `true` if `value.$type` matches `type` or any of its ancestors in the hierarchy.
53
135
 
54
- ---
136
+ ```ts
137
+ if (is(rover, $Dog)) {
138
+ // TypeScript narrows: rover is now typed as Dog
139
+ console.log(rover.name); // ✅ safe
140
+ }
141
+ ```
142
+
143
+ ### `isType(a, b)` → `boolean`
144
+
145
+ Low-level comparison between two `$Type` descriptors. Handles inheritance chains and generic type matching internally.
146
+
147
+ ### `$Type<T>` Interface
148
+
149
+ ```ts
150
+ interface $Type<T = unknown> {
151
+ name: string;
152
+ type?: T;
153
+ parent?: $Type | null;
154
+ generics?: $Type[] | null;
155
+ }
156
+ ```
157
+
158
+ ### `Brand<T>` Type
159
+
160
+ A structural branding utility for compile-time + runtime safety:
161
+
162
+ ```ts
163
+ import {Brand} from 'rtt';
164
+
165
+ type UserId = Brand<'UserId'> & number;
166
+ ```
167
+
168
+ ## Development
169
+
170
+ ```bash
171
+ npm install
172
+ npm test # Run vitest suite
173
+ npm run build # Build with Vite + dts (generates dist/)
174
+ ```
175
+
176
+ ## License
177
+
178
+ [MIT](LICENSE) — © [Nizami](https://github.com/nizami)
package/dist/index.js CHANGED
@@ -1,34 +1,25 @@
1
- function is(model, type) {
2
- return "$type" in model && isType(model?.$type, type);
1
+ //#region src/is/is.ts
2
+ function is(value, type) {
3
+ return "$type" in value && isType(value?.$type, type);
3
4
  }
4
5
  function isType(a, b) {
5
- if (nameEquals(b, $Model()) || a.parent && isType(a.parent, b)) {
6
- return true;
7
- }
8
- if (b.generics && b.generics.length > 0) {
9
- if (a.generics && a.generics.length === b.generics.length) {
10
- return a.generics.every((x, i) => isType(x, b.generics[i]));
11
- }
12
- return false;
13
- }
14
- return nameEquals(a, b);
6
+ if (a.parent && isType(a.parent, b)) return true;
7
+ if (b.generics && b.generics.length > 0) {
8
+ if (a.generics && a.generics.length === b.generics.length) return a.generics.every((x, i) => isType(x, b.generics[i]));
9
+ return false;
10
+ }
11
+ return a.name === b.name;
15
12
  }
16
- function nameEquals(a, b) {
17
- return a.typeName === b.typeName && a.packageName == b.packageName;
13
+ //#endregion
14
+ //#region src/type/type.ts
15
+ function $type(name, parent, generics) {
16
+ return {
17
+ name,
18
+ parent,
19
+ generics
20
+ };
18
21
  }
19
- const $Model = () => $NewType("RuntimeType", "Model");
20
- function $NewType(packageName, typeName, parent, generics) {
21
- return {
22
- packageName,
23
- typeName,
24
- parent,
25
- generics
26
- };
27
- }
28
- export {
29
- $Model,
30
- $NewType,
31
- is,
32
- isType
33
- };
34
- //# sourceMappingURL=index.js.map
22
+ //#endregion
23
+ export { $type, is, isType };
24
+
25
+ //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":["../src/is/is.ts","../src/model.ts","../src/type/type.factory.ts"],"sourcesContent":["import {$Model, $Type} from '#lib';\n\nexport function is<T extends $Type>(model: any, type: T): model is Exclude<T['type'], undefined> {\n return '$type' in model && isType(model?.$type, type);\n}\n\nexport function isType(a: $Type, b: $Type): boolean {\n if ( nameEquals(b, $Model()) || (a.parent && isType(a.parent, b))) {\n return true;\n }\n\n if (b.generics && b.generics.length > 0) {\n if (a.generics && a.generics.length === b.generics.length) {\n // todo Covariance and Contravariance ???\n return a.generics.every((x, i) => isType(x, b.generics![i]));\n }\n\n return false;\n }\n\n return nameEquals(a, b);\n}\n\nfunction nameEquals(a: $Type, b: $Type): boolean {\n return a.typeName === b.typeName && a.packageName == b.packageName;\n}\n","import {$NewType, $Type} from '#lib';\n\nexport interface Model {\n $type: $Type;\n}\n\nexport const $Model = () => $NewType<Model>('RuntimeType', 'Model');\n","import {$Type, Model} from '#lib';\n\nexport function $NewType<T extends Model>(\n packageName: string,\n typeName: string,\n parent?: $Type | null | undefined,\n generics?: $Type[] | null | undefined,\n): $Type<T> {\n return {\n packageName,\n typeName,\n parent,\n generics,\n };\n}\n"],"names":[],"mappings":"AAEO,SAAS,GAAoB,OAAY,MAAiD;AAC/F,SAAO,WAAW,SAAS,OAAO,OAAO,OAAO,IAAI;AACtD;AAEO,SAAS,OAAO,GAAU,GAAmB;AAClD,MAAK,WAAW,GAAG,OAAA,CAAQ,KAAM,EAAE,UAAU,OAAO,EAAE,QAAQ,CAAC,GAAI;AACjE,WAAO;AAAA,EACT;AAEA,MAAI,EAAE,YAAY,EAAE,SAAS,SAAS,GAAG;AACvC,QAAI,EAAE,YAAY,EAAE,SAAS,WAAW,EAAE,SAAS,QAAQ;AAEzD,aAAO,EAAE,SAAS,MAAM,CAAC,GAAG,MAAM,OAAO,GAAG,EAAE,SAAU,CAAC,CAAC,CAAC;AAAA,IAC7D;AAEA,WAAO;AAAA,EACT;AAEA,SAAO,WAAW,GAAG,CAAC;AACxB;AAEA,SAAS,WAAW,GAAU,GAAmB;AAC/C,SAAO,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE;AACzD;ACnBO,MAAM,SAAS,MAAM,SAAgB,eAAe,OAAO;ACJ3D,SAAS,SACd,aACA,UACA,QACA,UACU;AACV,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA;AAEJ;"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/is/is.ts","../src/type/type.ts"],"sourcesContent":["import {$Type} from '../type/type';\n\nexport function is<T extends $Type>(value: any, type: T): value is Exclude<T['type'], undefined> {\n return '$type' in value && isType(value?.$type, type);\n}\n\nexport function isType(a: $Type, b: $Type): boolean {\n if (a.parent && isType(a.parent, b)) {\n return true;\n }\n\n if (b.generics && b.generics.length > 0) {\n if (a.generics && a.generics.length === b.generics.length) {\n // todo Covariance and Contravariance ???\n return a.generics.every((x, i) => isType(x, b.generics![i]));\n }\n\n return false;\n }\n\n return a.name === b.name;\n}\n","export interface $Type<T = unknown> {\n name: string;\n type?: T;\n parent?: $Type | null | undefined;\n generics?: $Type[] | null | undefined;\n}\n\nexport function $type<T = unknown>(\n name: string,\n parent?: $Type | null | undefined,\n generics?: $Type[] | null | undefined,\n): $Type<T> {\n return {name, parent, generics};\n}\n"],"mappings":";AAEA,SAAgB,GAAoB,OAAY,MAAiD;CAC/F,OAAO,WAAW,SAAS,OAAO,OAAO,OAAO,IAAI;AACtD;AAEA,SAAgB,OAAO,GAAU,GAAmB;CAClD,IAAI,EAAE,UAAU,OAAO,EAAE,QAAQ,CAAC,GAChC,OAAO;CAGT,IAAI,EAAE,YAAY,EAAE,SAAS,SAAS,GAAG;EACvC,IAAI,EAAE,YAAY,EAAE,SAAS,WAAW,EAAE,SAAS,QAEjD,OAAO,EAAE,SAAS,OAAO,GAAG,MAAM,OAAO,GAAG,EAAE,SAAU,EAAE,CAAC;EAG7D,OAAO;CACT;CAEA,OAAO,EAAE,SAAS,EAAE;AACtB;;;ACdA,SAAgB,MACd,MACA,QACA,UACU;CACV,OAAO;EAAC;EAAM;EAAQ;CAAQ;AAChC"}
@@ -1,5 +1,5 @@
1
1
  declare const brand: unique symbol;
2
- export type Brand<T extends `${string}.${string}`> = {
2
+ export type Brand<T extends string> = {
3
3
  [brand]?: T;
4
4
  };
5
5
  export {};
@@ -1,5 +1,3 @@
1
1
  export * from './brand';
2
2
  export * from './is/is';
3
- export * from './model';
4
- export * from './type/type.factory';
5
3
  export * from './type/type';
@@ -0,0 +1,3 @@
1
+ import { $Type } from '../type/type';
2
+ export declare function is<T extends $Type>(value: any, type: T): value is Exclude<T['type'], undefined>;
3
+ export declare function isType(a: $Type, b: $Type): boolean;
@@ -0,0 +1,7 @@
1
+ export interface $Type<T = unknown> {
2
+ name: string;
3
+ type?: T;
4
+ parent?: $Type | null | undefined;
5
+ generics?: $Type[] | null | undefined;
6
+ }
7
+ export declare function $type<T = unknown>(name: string, parent?: $Type | null | undefined, generics?: $Type[] | null | undefined): $Type<T>;
package/package.json CHANGED
@@ -1,44 +1,47 @@
1
1
  {
2
- "name": "rtt",
3
- "version": "1.0.2",
2
+ "author": "Nizami",
3
+ "bugs": {
4
+ "url": "https://github.com/nizami/rtt/issues"
5
+ },
4
6
  "description": "Runtime Typescript Types",
5
- "main": "index.js",
6
- "scripts": {
7
- "build": "barrelize && vite build",
8
- "test": "vitest run"
7
+ "devDependencies": {
8
+ "@types/node": "^25.9.3",
9
+ "barrelize": "^1.8.1",
10
+ "typescript": "~6.0.3",
11
+ "vite": "^8.0.16",
12
+ "vite-plugin-dts": "^5.0.2",
13
+ "vitest": "^4.1.8"
9
14
  },
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/nizami/rtt.git"
15
+ "exports": {
16
+ ".": {
17
+ "import": "./dist/index.js",
18
+ "types": "./dist/index.d.ts"
19
+ }
13
20
  },
21
+ "files": [
22
+ "dist"
23
+ ],
24
+ "homepage": "https://github.com/nizami/rtt#readme",
14
25
  "keywords": [
15
26
  "Typescript",
16
27
  "Runtime Types"
17
28
  ],
18
- "author": "Nizami",
19
29
  "license": "MIT",
20
- "bugs": {
21
- "url": "https://github.com/nizami/rtt/issues"
30
+ "main": "index.js",
31
+ "name": "rtt",
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/nizami/rtt.git"
22
35
  },
23
- "homepage": "https://github.com/nizami/rtt#readme",
24
- "devDependencies": {
25
- "@types/node": "^24.0.10",
26
- "barrelize": "^1.6.2",
27
- "typescript": "~5.8.3",
28
- "vite": "^7.0.2",
29
- "vite-plugin-dts": "^4.5.4",
30
- "vitest": "^3.2.4"
36
+ "scripts": {
37
+ "build": "barrelize && vite build",
38
+ "test": "vitest run"
31
39
  },
32
- "type": "module",
33
40
  "sideEffects": false,
34
- "files": [
35
- "dist"
36
- ],
41
+ "type": "module",
37
42
  "types": "./dist/index.d.ts",
38
- "exports": {
39
- ".": {
40
- "types": "./dist/index.d.ts",
41
- "import": "./dist/index.js"
42
- }
43
+ "version": "1.1.1",
44
+ "allowScripts": {
45
+ "fsevents@2.3.3": true
43
46
  }
44
47
  }
package/dist/is/is.d.ts DELETED
@@ -1,3 +0,0 @@
1
- import { $Type } from '../index.ts';
2
- export declare function is<T extends $Type>(model: any, type: T): model is Exclude<T['type'], undefined>;
3
- export declare function isType(a: $Type, b: $Type): boolean;
package/dist/model.d.ts DELETED
@@ -1,5 +0,0 @@
1
- import { $Type } from './index.ts';
2
- export interface Model {
3
- $type: $Type;
4
- }
5
- export declare const $Model: () => $Type<Model>;
@@ -1,7 +0,0 @@
1
- export interface $Type<T = unknown> {
2
- packageName: string;
3
- typeName: string;
4
- type?: T;
5
- parent?: $Type | null | undefined;
6
- generics?: $Type[] | null | undefined;
7
- }
@@ -1,2 +0,0 @@
1
- import { $Type, Model } from '../index.ts';
2
- export declare function $NewType<T extends Model>(packageName: string, typeName: string, parent?: $Type | null | undefined, generics?: $Type[] | null | undefined): $Type<T>;
File without changes
File without changes