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 +153 -29
- package/dist/index.js +21 -30
- package/dist/index.js.map +1 -1
- package/dist/{brand.d.ts → src/brand.d.ts} +1 -1
- package/dist/{index.d.ts → src/index.d.ts} +0 -2
- package/dist/src/is/is.d.ts +3 -0
- package/dist/src/type/type.d.ts +7 -0
- package/package.json +32 -29
- package/dist/is/is.d.ts +0 -3
- package/dist/model.d.ts +0 -5
- package/dist/type/type.d.ts +0 -7
- package/dist/type/type.factory.d.ts +0 -2
- /package/dist/{is → src/is}/is.test.d.ts +0 -0
- /package/dist/{type → src/type}/type.test.d.ts +0 -0
package/README.md
CHANGED
|
@@ -1,54 +1,178 @@
|
|
|
1
|
-
# rtt
|
|
1
|
+
# rtt — Runtime Types for TypeScript
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/rtt)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
> **TypeScript types vanish at runtime.** `rtt` brings them back — with zero dependencies and a tiny footprint.
|
|
6
7
|
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
24
|
-
import {$
|
|
88
|
+
```ts
|
|
89
|
+
import {$type, is} from 'rtt';
|
|
25
90
|
|
|
26
|
-
// Define types
|
|
27
|
-
const $
|
|
28
|
-
const $
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
$type: $B(),
|
|
33
|
-
};
|
|
95
|
+
export type User = {$type: $Type; name: string};
|
|
96
|
+
export type List<T> = {$type: $Type; items: T[]};
|
|
34
97
|
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
### `$
|
|
117
|
+
### `$type<T>(name, parent?, generics?)` → `$Type<T>`
|
|
43
118
|
|
|
44
|
-
Creates a
|
|
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
|
-
|
|
121
|
+
```ts
|
|
122
|
+
const $Animal = $type<Animal>('app.Animal');
|
|
123
|
+
const $Dog = $type<Dog>('app.Dog', $Animal);
|
|
124
|
+
```
|
|
47
125
|
|
|
48
|
-
|
|
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
|
-
### `
|
|
132
|
+
### `is<T>(value, type)` → `value is T`
|
|
51
133
|
|
|
52
|
-
|
|
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
|
-
|
|
2
|
-
|
|
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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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/
|
|
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"}
|
|
@@ -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
|
-
"
|
|
3
|
-
"
|
|
2
|
+
"author": "Nizami",
|
|
3
|
+
"bugs": {
|
|
4
|
+
"url": "https://github.com/nizami/rtt/issues"
|
|
5
|
+
},
|
|
4
6
|
"description": "Runtime Typescript Types",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
"
|
|
8
|
-
"
|
|
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
|
-
"
|
|
11
|
-
"
|
|
12
|
-
|
|
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
|
-
"
|
|
21
|
-
|
|
30
|
+
"main": "index.js",
|
|
31
|
+
"name": "rtt",
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/nizami/rtt.git"
|
|
22
35
|
},
|
|
23
|
-
"
|
|
24
|
-
|
|
25
|
-
"
|
|
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
|
-
"
|
|
35
|
-
"dist"
|
|
36
|
-
],
|
|
41
|
+
"type": "module",
|
|
37
42
|
"types": "./dist/index.d.ts",
|
|
38
|
-
"
|
|
39
|
-
|
|
40
|
-
|
|
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
package/dist/model.d.ts
DELETED
package/dist/type/type.d.ts
DELETED
|
File without changes
|
|
File without changes
|