@elliots/typical 0.1.10 → 0.2.0
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 +234 -200
- package/dist/src/cli.js +12 -85
- package/dist/src/cli.js.map +1 -1
- package/dist/src/cli.typical.ts +136 -0
- package/dist/src/config.d.ts +12 -0
- package/dist/src/config.js +40 -38
- package/dist/src/config.js.map +1 -1
- package/dist/src/config.typical.ts +287 -0
- package/dist/src/esm-loader-register.js.map +1 -1
- package/dist/src/esm-loader.d.ts +1 -1
- package/dist/src/esm-loader.js +30 -17
- package/dist/src/esm-loader.js.map +1 -1
- package/dist/src/file-filter.d.ts +1 -1
- package/dist/src/file-filter.js.map +1 -1
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.js +1 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/program-manager.d.ts +27 -0
- package/dist/src/program-manager.js +121 -0
- package/dist/src/program-manager.js.map +1 -0
- package/dist/src/regex-hoister.d.ts +1 -1
- package/dist/src/regex-hoister.js +13 -19
- package/dist/src/regex-hoister.js.map +1 -1
- package/dist/src/setup.d.ts +1 -1
- package/dist/src/setup.js +3 -3
- package/dist/src/setup.js.map +1 -1
- package/dist/src/source-map.d.ts +1 -1
- package/dist/src/source-map.js +1 -1
- package/dist/src/source-map.js.map +1 -1
- package/dist/src/source-map.typical.ts +216 -0
- package/dist/src/timing.d.ts +19 -0
- package/dist/src/timing.js +65 -0
- package/dist/src/timing.js.map +1 -0
- package/dist/src/transformer.d.ts +28 -193
- package/dist/src/transformer.js +41 -1917
- package/dist/src/transformer.js.map +1 -1
- package/dist/src/transformer.typical.ts +2552 -0
- package/dist/src/tsc-plugin.d.ts +8 -1
- package/dist/src/tsc-plugin.js +11 -7
- package/dist/src/tsc-plugin.js.map +1 -1
- package/package.json +51 -47
- package/src/cli.ts +41 -128
- package/src/config.ts +106 -91
- package/src/esm-loader-register.ts +2 -2
- package/src/esm-loader.ts +44 -29
- package/src/index.ts +5 -10
- package/src/patch-fs.cjs +14 -14
- package/src/timing.ts +74 -0
- package/src/transformer.ts +47 -2592
- package/bin/ttsc +0 -12
- package/src/file-filter.ts +0 -49
- package/src/patch-tsconfig.cjs +0 -52
- package/src/regex-hoister.ts +0 -203
- package/src/setup.ts +0 -39
- package/src/source-map.ts +0 -202
- package/src/tsc-plugin.ts +0 -12
package/README.md
CHANGED
|
@@ -1,113 +1,129 @@
|
|
|
1
1
|
# Typical
|
|
2
2
|
|
|
3
|
-
Typical
|
|
3
|
+
Typical makes TypeScript type-safe at runtime _with no changes to your code_.
|
|
4
4
|
|
|
5
|
-
It
|
|
5
|
+
It transforms your code to inject runtime validation based on your existing type annotations. With source maps, so errors point to the right lines in your original code.
|
|
6
6
|
|
|
7
7
|
## Why?
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
- Less need for zod, yup, ajv, or other runtime validation libraries - your types are already validated automatically. If you can express it in TypeScript, Typical can validate it at runtime.
|
|
10
|
+
- Protects against data leaks via `JSON.stringify` by ensuring only properties defined in your types are included
|
|
11
|
+
- Catches type mismatches at runtime that TypeScript can't catch at compile time (API responses, JSON parsing, un-typed/badly-typed libraries, vibe-coding coworkers etc.)
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
## Features
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
- Validation of function parameters and return types
|
|
16
|
+
- Safe `JSON.parse` with type validation
|
|
17
|
+
- Safe `JSON.stringify` that only includes defined properties
|
|
18
|
+
- Validation of type casts (`as Type`)
|
|
19
|
+
- Configurable include/exclude patterns
|
|
14
20
|
|
|
15
|
-
##
|
|
21
|
+
## Example
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
- ✅ Automatic validation of return types
|
|
19
|
-
- ✅ Replace `JSON.stringify` with a custom stringifier (very fast!)
|
|
20
|
-
- ✅ Replace `JSON.parse` with a custom parser and validator (very fast!)
|
|
21
|
-
- ✅ Configurable include/exclude patterns
|
|
22
|
-
- ✅ Optionally reuse validation logic for identical types to optimize performance (enabled by default)
|
|
23
|
-
- ✅ TSC plugin
|
|
24
|
-
- ✅ ESM loader for runtime transformation with `node --import @elliots/typical/esm` (or `node --loader @elliots/typical/esm-loader` for older Node versions)
|
|
25
|
-
- ✅ tsx wrapper (ttsx) for easy use like `npx ttsx script.ts`
|
|
26
|
-
- ✅ Unplugin for Vite, Webpack, Rollup, esbuild, and more
|
|
23
|
+
This code runs without errors in normal TypeScript, but Typical catches the invalid data:
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
```ts
|
|
26
|
+
interface User {
|
|
27
|
+
name: string;
|
|
28
|
+
email: `${string}@${string}`;
|
|
29
|
+
}
|
|
29
30
|
|
|
30
|
-
|
|
31
|
-
|
|
31
|
+
// This will throw - email doesn't match the template literal type
|
|
32
|
+
const user = JSON.parse('{"name":"Alice","email":"not-an-email"}') as User;
|
|
32
33
|
```
|
|
33
34
|
|
|
34
|
-
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
## Usage Options
|
|
38
|
+
|
|
39
|
+
Choose the integration that fits your workflow:
|
|
40
|
+
|
|
41
|
+
| Method | Best For | Package |
|
|
42
|
+
| --------------------------------------------------------- | ------------------------------- | ----------------------------- |
|
|
43
|
+
| [ESM Loader](#nodejs-esm-loader) | Node.js scripts, development | `@elliots/typical` |
|
|
44
|
+
| [ttsx](#ttsx-tsx-wrapper) | Quick scripts with tsx | `@elliots/typical` + `tsx` |
|
|
45
|
+
| [Bun Plugin](#bun) | Bun projects | `@elliots/bun-plugin-typical` |
|
|
46
|
+
| [Vite/Webpack/etc](#bundlers-vite-webpack-rollup-esbuild) | Frontend apps, bundled projects | `@elliots/unplugin-typical` |
|
|
47
|
+
| [tsc Plugin](#typescript-compiler-tsc) | Pure TypeScript compilation | `@elliots/typical-tsc-plugin` |
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Node.js (ESM Loader)
|
|
35
52
|
|
|
36
|
-
|
|
53
|
+
The simplest way to run TypeScript with Typical validation.
|
|
37
54
|
|
|
38
|
-
|
|
55
|
+
```bash
|
|
56
|
+
npm add @elliots/typical
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
node --import @elliots/typical/esm src/index.ts
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Add to `package.json` scripts:
|
|
39
64
|
|
|
40
65
|
```json
|
|
41
66
|
{
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
"reusableValidators": false,
|
|
45
|
-
"validateFunctions": true,
|
|
46
|
-
"validateCasts": false,
|
|
47
|
-
"hoistRegex": true,
|
|
48
|
-
"ignoreDOMTypes": true,
|
|
49
|
-
"ignoreTypes": [],
|
|
50
|
-
"sourceMap": {
|
|
51
|
-
"enabled": true,
|
|
52
|
-
"includeContent": true,
|
|
53
|
-
"inline": false
|
|
67
|
+
"scripts": {
|
|
68
|
+
"start": "node --import @elliots/typical/esm src/index.ts"
|
|
54
69
|
}
|
|
55
70
|
}
|
|
56
71
|
```
|
|
57
72
|
|
|
58
|
-
|
|
73
|
+
---
|
|
59
74
|
|
|
60
|
-
|
|
61
|
-
|--------|---------|-------------|
|
|
62
|
-
| `include` | `["**/*.ts", "**/*.tsx"]` | Glob patterns for files to transform |
|
|
63
|
-
| `exclude` | `["node_modules/**", "**/*.d.ts", "dist/**", "build/**"]` | Glob patterns for files to skip |
|
|
64
|
-
| `reusableValidators` | `false` | Create shared validators for identical types (smaller output). Incompatible with source maps. |
|
|
65
|
-
| `validateFunctions` | `true` | Validate function parameters and return types at runtime |
|
|
66
|
-
| `validateCasts` | `false` | Validate type assertions (`as Type`) at runtime |
|
|
67
|
-
| `hoistRegex` | `true` | Hoist regex patterns to top-level constants (improves performance) |
|
|
68
|
-
| `ignoreDOMTypes` | `true` | Skip validation for DOM types (Document, Element, etc.) |
|
|
69
|
-
| `ignoreTypes` | `[]` | Type patterns to skip validation for (supports wildcards, e.g., `["React.*"]`) |
|
|
70
|
-
| `sourceMap.enabled` | `true` | Generate source maps for transformed code |
|
|
71
|
-
| `sourceMap.includeContent` | `true` | Include original source content in source maps |
|
|
72
|
-
| `sourceMap.inline` | `false` | Use inline source maps (data URL) instead of external files |
|
|
73
|
-
| `debug.writeIntermediateFiles` | `false` | Write `.typical.ts` files showing code before typia transform |
|
|
75
|
+
## ttsx (tsx wrapper)
|
|
74
76
|
|
|
75
|
-
|
|
77
|
+
A convenience wrapper that combines [tsx](https://github.com/privatenumber/tsx) with Typical.
|
|
76
78
|
|
|
77
|
-
|
|
79
|
+
```bash
|
|
80
|
+
npm add @elliots/typical tsx
|
|
81
|
+
```
|
|
78
82
|
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
"reusableValidators": true,
|
|
82
|
-
"sourceMap": {
|
|
83
|
-
"enabled": false
|
|
84
|
-
}
|
|
85
|
-
}
|
|
83
|
+
```bash
|
|
84
|
+
npx ttsx script.ts
|
|
86
85
|
```
|
|
87
86
|
|
|
88
|
-
|
|
87
|
+
Or install globally:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
npm add -g @elliots/typical tsx
|
|
91
|
+
ttsx script.ts
|
|
92
|
+
```
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
> **Note:** `tsx` must be installed separately. The `ttsx` command is a thin wrapper that runs `tsx` with the Typical ESM loader.
|
|
91
95
|
|
|
92
|
-
|
|
96
|
+
---
|
|
93
97
|
|
|
94
|
-
|
|
98
|
+
## Bun
|
|
95
99
|
|
|
96
100
|
```bash
|
|
97
|
-
|
|
98
|
-
|
|
101
|
+
bun add @elliots/bun-plugin-typical
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Create `bunfig.toml`:
|
|
105
|
+
|
|
106
|
+
```toml
|
|
107
|
+
preload = ["./preload.ts"]
|
|
99
108
|
```
|
|
100
109
|
|
|
101
|
-
|
|
110
|
+
Create `preload.ts`:
|
|
111
|
+
|
|
112
|
+
```ts
|
|
113
|
+
import { typicalPlugin } from "@elliots/bun-plugin-typical";
|
|
114
|
+
|
|
115
|
+
Bun.plugin(typicalPlugin());
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Then run:
|
|
102
119
|
|
|
103
120
|
```bash
|
|
104
|
-
|
|
105
|
-
ttsx your-script.ts
|
|
121
|
+
bun run src/index.ts
|
|
106
122
|
```
|
|
107
123
|
|
|
108
|
-
|
|
124
|
+
---
|
|
109
125
|
|
|
110
|
-
|
|
126
|
+
## Bundlers (Vite, Webpack, Rollup, esbuild)
|
|
111
127
|
|
|
112
128
|
```bash
|
|
113
129
|
npm add @elliots/unplugin-typical
|
|
@@ -115,224 +131,242 @@ npm add @elliots/unplugin-typical
|
|
|
115
131
|
|
|
116
132
|
### Vite
|
|
117
133
|
|
|
118
|
-
```
|
|
134
|
+
```ts
|
|
119
135
|
// vite.config.ts
|
|
120
|
-
import Typical from
|
|
136
|
+
import Typical from "@elliots/unplugin-typical/vite";
|
|
121
137
|
|
|
122
138
|
export default defineConfig({
|
|
123
|
-
plugins: [
|
|
124
|
-
|
|
125
|
-
],
|
|
126
|
-
})
|
|
139
|
+
plugins: [Typical()],
|
|
140
|
+
});
|
|
127
141
|
```
|
|
128
142
|
|
|
129
143
|
### Webpack
|
|
130
144
|
|
|
131
|
-
```
|
|
145
|
+
```js
|
|
132
146
|
// webpack.config.js
|
|
133
|
-
const Typical = require(
|
|
147
|
+
const Typical = require("@elliots/unplugin-typical/webpack").default;
|
|
134
148
|
|
|
135
149
|
module.exports = {
|
|
136
|
-
plugins: [
|
|
137
|
-
|
|
138
|
-
],
|
|
139
|
-
}
|
|
150
|
+
plugins: [Typical()],
|
|
151
|
+
};
|
|
140
152
|
```
|
|
141
153
|
|
|
142
154
|
### Rollup
|
|
143
155
|
|
|
144
|
-
```
|
|
156
|
+
```js
|
|
145
157
|
// rollup.config.js
|
|
146
|
-
import Typical from
|
|
158
|
+
import Typical from "@elliots/unplugin-typical/rollup";
|
|
147
159
|
|
|
148
160
|
export default {
|
|
149
|
-
plugins: [
|
|
150
|
-
|
|
151
|
-
],
|
|
152
|
-
}
|
|
161
|
+
plugins: [Typical()],
|
|
162
|
+
};
|
|
153
163
|
```
|
|
154
164
|
|
|
155
165
|
### esbuild
|
|
156
166
|
|
|
157
|
-
```
|
|
158
|
-
import { build } from
|
|
159
|
-
import Typical from
|
|
167
|
+
```ts
|
|
168
|
+
import { build } from "esbuild";
|
|
169
|
+
import Typical from "@elliots/unplugin-typical/esbuild";
|
|
160
170
|
|
|
161
171
|
build({
|
|
162
172
|
plugins: [Typical()],
|
|
163
|
-
})
|
|
173
|
+
});
|
|
164
174
|
```
|
|
165
175
|
|
|
166
|
-
###
|
|
176
|
+
### Rolldown
|
|
167
177
|
|
|
168
|
-
|
|
178
|
+
```ts
|
|
179
|
+
// rolldown.config.ts
|
|
180
|
+
import Typical from "@elliots/unplugin-typical/rolldown";
|
|
169
181
|
|
|
170
|
-
|
|
171
|
-
Typical(
|
|
172
|
-
|
|
173
|
-
validateCasts: false,
|
|
174
|
-
// ... other options
|
|
175
|
-
})
|
|
182
|
+
export default {
|
|
183
|
+
plugins: [Typical()],
|
|
184
|
+
};
|
|
176
185
|
```
|
|
177
186
|
|
|
178
|
-
|
|
187
|
+
### Farm
|
|
179
188
|
|
|
180
|
-
|
|
189
|
+
```ts
|
|
190
|
+
// farm.config.ts
|
|
191
|
+
import Typical from "@elliots/unplugin-typical/farm";
|
|
181
192
|
|
|
182
|
-
|
|
193
|
+
export default {
|
|
194
|
+
plugins: [Typical()],
|
|
195
|
+
};
|
|
196
|
+
```
|
|
183
197
|
|
|
198
|
+
### Rspack
|
|
184
199
|
|
|
185
200
|
```ts
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
201
|
+
// rspack.config.ts
|
|
202
|
+
import Typical from "@elliots/unplugin-typical/rspack";
|
|
203
|
+
|
|
204
|
+
export default {
|
|
205
|
+
plugins: [Typical()],
|
|
206
|
+
};
|
|
191
207
|
```
|
|
192
208
|
|
|
193
|
-
|
|
209
|
+
---
|
|
194
210
|
|
|
195
|
-
|
|
211
|
+
## TypeScript Compiler (tsc)
|
|
196
212
|
|
|
197
|
-
|
|
213
|
+
For projects that compile with `tsc` directly using [ts-patch](https://github.com/nonara/ts-patch).
|
|
198
214
|
|
|
199
|
-
|
|
215
|
+
```bash
|
|
216
|
+
npm add @elliots/typical-tsc-plugin ts-patch
|
|
217
|
+
```
|
|
200
218
|
|
|
201
|
-
|
|
219
|
+
### Option 1: ttsc (auto-injects plugin)
|
|
202
220
|
|
|
203
|
-
|
|
221
|
+
The `ttsc` command automatically injects the plugin - no config needed:
|
|
204
222
|
|
|
205
|
-
|
|
223
|
+
```bash
|
|
224
|
+
npx ttsc
|
|
225
|
+
```
|
|
206
226
|
|
|
207
|
-
|
|
208
|
-
interface User { name: string; }
|
|
227
|
+
Add to `package.json`:
|
|
209
228
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"scripts": {
|
|
232
|
+
"build": "ttsc"
|
|
233
|
+
}
|
|
213
234
|
}
|
|
235
|
+
```
|
|
214
236
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
}
|
|
237
|
+
### Option 2: Manual tsconfig.json
|
|
238
|
+
|
|
239
|
+
Add to your `tsconfig.json`:
|
|
219
240
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
241
|
+
```json
|
|
242
|
+
{
|
|
243
|
+
"compilerOptions": {
|
|
244
|
+
"plugins": [
|
|
245
|
+
{
|
|
246
|
+
"transform": "@elliots/typical-tsc-plugin",
|
|
247
|
+
"transformProgram": true
|
|
248
|
+
}
|
|
249
|
+
]
|
|
250
|
+
}
|
|
224
251
|
}
|
|
225
252
|
```
|
|
226
253
|
|
|
227
|
-
|
|
254
|
+
Then run ts-patch's tsc:
|
|
228
255
|
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
return user; // Must re-validate
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// After passing to another function
|
|
237
|
-
function processUser(user: User): User {
|
|
238
|
-
someFunction(user); // Could have mutated user
|
|
239
|
-
return user; // Must re-validate
|
|
240
|
-
}
|
|
256
|
+
```bash
|
|
257
|
+
npx ts-patch install
|
|
258
|
+
npx tsc
|
|
259
|
+
```
|
|
241
260
|
|
|
242
|
-
|
|
243
|
-
async function asyncProcess(user: User): Promise<User> {
|
|
244
|
-
await delay(); // Async boundary taints values
|
|
245
|
-
return user; // Must re-validate
|
|
246
|
-
}
|
|
261
|
+
Or add a prepare script:
|
|
247
262
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
263
|
+
```json
|
|
264
|
+
{
|
|
265
|
+
"scripts": {
|
|
266
|
+
"prepare": "ts-patch install -s",
|
|
267
|
+
"build": "tsc"
|
|
268
|
+
}
|
|
251
269
|
}
|
|
252
270
|
```
|
|
253
271
|
|
|
254
|
-
|
|
272
|
+
---
|
|
255
273
|
|
|
256
|
-
|
|
274
|
+
## Configuration
|
|
257
275
|
|
|
258
|
-
|
|
276
|
+
Create a `typical.json` file in your project root (optional):
|
|
259
277
|
|
|
260
278
|
```json
|
|
261
279
|
{
|
|
262
|
-
"
|
|
263
|
-
|
|
264
|
-
|
|
280
|
+
"include": ["**/*.ts", "**/*.tsx"],
|
|
281
|
+
"exclude": ["node_modules/**", "**/*.d.ts"],
|
|
282
|
+
"validateFunctions": true,
|
|
283
|
+
"validateCasts": false
|
|
265
284
|
}
|
|
266
285
|
```
|
|
267
286
|
|
|
268
|
-
|
|
287
|
+
### Options
|
|
269
288
|
|
|
270
|
-
|
|
289
|
+
| Option | Default | Description |
|
|
290
|
+
| ------------------------ | --------------------------------------------------------- | ----------------------------------------------------------------- |
|
|
291
|
+
| `include` | `["**/*.ts", "**/*.tsx"]` | Files to transform |
|
|
292
|
+
| `exclude` | `["node_modules/**", "**/*.d.ts", "dist/**", "build/**"]` | Files to skip |
|
|
293
|
+
| `validateFunctions` | `true` | Validate function parameters and return types |
|
|
294
|
+
| `validateCasts` | `false` | Validate type assertions (`as Type`) |
|
|
295
|
+
| `transformJSONParse` | `true` | Transform `JSON.parse` to validate and filter to typed properties |
|
|
296
|
+
| `transformJSONStringify` | `true` | Transform `JSON.stringify` to only include typed properties |
|
|
271
297
|
|
|
272
|
-
|
|
298
|
+
---
|
|
273
299
|
|
|
274
|
-
|
|
275
|
-
DEBUG=1 npm run build
|
|
276
|
-
```
|
|
300
|
+
## JSON Transformations
|
|
277
301
|
|
|
278
|
-
|
|
302
|
+
Typical automatically transforms `JSON.parse` and `JSON.stringify` calls when type information is available.
|
|
279
303
|
|
|
280
|
-
###
|
|
304
|
+
### JSON.parse
|
|
281
305
|
|
|
282
|
-
|
|
306
|
+
When you cast the result of `JSON.parse`, Typical validates the parsed data and filters it to only include properties defined in your type:
|
|
283
307
|
|
|
284
|
-
|
|
285
|
-
|
|
308
|
+
```ts
|
|
309
|
+
interface User {
|
|
310
|
+
name: string;
|
|
311
|
+
age: number;
|
|
312
|
+
}
|
|
286
313
|
|
|
287
|
-
|
|
288
|
-
|
|
314
|
+
// Input: '{"name":"Alice","age":30,"password":"secret"}'
|
|
315
|
+
const user = JSON.parse(jsonString) as User;
|
|
316
|
+
// Result: { name: "Alice", age: 30 } - password is filtered out!
|
|
317
|
+
// Throws TypeError if name isn't a string or age isn't a number
|
|
318
|
+
```
|
|
289
319
|
|
|
290
|
-
|
|
291
|
-
- Solution: Add the specific type patterns to `ignoreTypes`
|
|
320
|
+
### JSON.stringify
|
|
292
321
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
322
|
+
When you use a type assertion with `JSON.stringify`, only properties defined in your type are included - preventing accidental data leaks:
|
|
323
|
+
|
|
324
|
+
```ts
|
|
325
|
+
interface PublicUser {
|
|
326
|
+
name: string;
|
|
327
|
+
age: number;
|
|
296
328
|
}
|
|
329
|
+
|
|
330
|
+
const user = { name: "Alice", age: 30, password: "secret", ssn: "123-45-6789" };
|
|
331
|
+
const json = JSON.stringify(user as PublicUser);
|
|
332
|
+
// Result: '{"name":"Alice","age":30}' - sensitive data excluded!
|
|
297
333
|
```
|
|
298
334
|
|
|
299
|
-
|
|
335
|
+
Both patterns detect type information from:
|
|
300
336
|
|
|
301
|
-
|
|
337
|
+
- Type assertions: `JSON.parse(str) as User` or `JSON.stringify(obj as User)`
|
|
338
|
+
- Variable declarations: `const user: User = JSON.parse(str)`
|
|
339
|
+
- Function return types: `function getUser(): User { return JSON.parse(str) }`
|
|
302
340
|
|
|
303
|
-
|
|
341
|
+
---
|
|
304
342
|
|
|
305
|
-
|
|
343
|
+
## How It Works
|
|
306
344
|
|
|
307
|
-
|
|
308
|
-
function identity<T>(value: T): T {
|
|
309
|
-
return value; // T is not validated - no runtime type info
|
|
310
|
-
}
|
|
311
|
-
```
|
|
345
|
+
Typical uses a Go-based compiler that leverages the TypeScript type checker to analyze your code. It generates runtime validators that check values against their declared types.
|
|
312
346
|
|
|
313
|
-
|
|
347
|
+
Types that can't be validated at runtime (like generic type parameters `T`) are skipped. You can still use `any` and `unknown` to opt out of validation.
|
|
314
348
|
|
|
315
|
-
|
|
316
|
-
function process<T>(value: T, user: User): User {
|
|
317
|
-
return user; // User IS validated
|
|
318
|
-
}
|
|
319
|
-
```
|
|
349
|
+
## Compiler Optimizations
|
|
320
350
|
|
|
321
|
-
|
|
351
|
+
The generated validation code is optimized for runtime performance:
|
|
322
352
|
|
|
323
|
-
|
|
353
|
+
- **Skip redundant validation** - When returning a validated parameter directly, the return validation is skipped (e.g., `return x` where `x: string` was already validated)
|
|
354
|
+
- **Subtype-aware skipping** - If a validated type is assignable to the return type, validation is skipped (e.g., `string` parameter returned as `string | null`)
|
|
355
|
+
- **Property chain tracking** - Accessing properties of validated objects skips re-validation (e.g., `return user.name` when `user: User` was validated)
|
|
356
|
+
- **Type-aware dirty tracking** - Primitives stay validated after being passed to functions (they're copied), but objects are re-validated (they could be mutated)
|
|
357
|
+
- **Union early bail-out** - Union type checks use if-else chains so the first matching type succeeds immediately
|
|
324
358
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
// Manual validation if needed
|
|
329
|
-
if (!options.timeout) throw new Error("timeout required");
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
```
|
|
359
|
+
## Debugging
|
|
360
|
+
|
|
361
|
+
Set `DEBUG=1` for verbose logging:
|
|
333
362
|
|
|
334
|
-
|
|
335
|
-
|
|
363
|
+
```bash
|
|
364
|
+
DEBUG=1 npm run build
|
|
365
|
+
```
|
|
336
366
|
|
|
337
|
-
|
|
367
|
+
## Limitations
|
|
338
368
|
|
|
369
|
+
- Generic type parameters (`T`) cannot be validated - no runtime type information
|
|
370
|
+
- Type-only imports of classes aren't checked (can't do instanceof on type-only imports)
|
|
371
|
+
- Validation of functions is just not done. Need to think about that one.
|
|
372
|
+
- Some complex types may not be fully supported yet. If you find any that fail, please open an issue!
|